如何在没有 OutOfMemory 错误的情况下从 FileInputStream 获取字节数组

Posted

技术标签:

【中文标题】如何在没有 OutOfMemory 错误的情况下从 FileInputStream 获取字节数组【英文标题】:How to get a byte array from FileInputStream without OutOfMemory error 【发布时间】:2013-03-27 09:25:19 【问题描述】:

我有一个包含 200MB 数据的 FileInputStream。我必须从输入流中检索字节。

我正在使用下面的代码将 InputStream 转换为字节数组。

private byte[] convertStreamToByteArray(InputStream inputStream) 
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try 
        int i;
        while ((i = inputStream.read()) > 0) 
            bos.write(i);
        
     catch (IOException e) 
        e.printStackTrace();
    
    return bos.toByteArray();

在将如此大的数据转换为字节数组时,我遇到了 OutOfMemory 异常。

请告诉我任何将 InputStream 转换为字节数组的可能解决方案。

【问题讨论】:

为什么需要在 RAM 中加载 200 MB?任何使用此应用的 android 单元都会崩溃。 内存不足错误是因为您试图一次将所有内容都保存在内存中。更改您尝试将其转换为字节数组的 方式 无济于事;问题是转换为字节数组的想法。 其实有一个场景需要向服务器发送大数据。我有一个解决方案来处理那部分。如果您对上述查询有任何解决方案,请告诉我。谢谢。 您可以读取文件的一部分,将其发送到服务器并重复该过程,直到没有更多内容可读取。此外,当您阅读 InputStream 时,您应该使用 byte[1024*X] buffer,其中 X 的值应为 1、2、4 或 8。AFAIK 1024*4 是最快的之一。 如果您需要将大文件发送到服务器(而您的客户会喜欢传输 200MB 的数据费用!),只需将其即时写入服务器连接即可阅读它。 【参考方案1】:

为什么要将 200MB 的文件保存在内存中?你打算用字节数组做什么?

如果要将其写入 OutputStream,请先准备好 OutputStream,然后一次读取 InputStream 一个块,然后将块写入 OutputStream。你永远不会在内存中存储超过块。

例如:

     public static void pipe(InputStream is, OutputStream os) throws IOException 

        int read = -1;
        byte[] buf = new byte[1024];

        try 
            while( (read = is.read(buf)) != -1) 
                os.write(buf, 0, read);
            
        
        finally 
            is.close();
            os.close();
        
    

此代码将采用两个流并将一个流传输到另一个。

【讨论】:

不错的代码,但您仍然将所有 200MB 保存在内存中。您应该在 while 循环中添加 os.flush() 调用。 "你还在把所有的 200MB 保存在内存中"。不完全正确 - 这取决于 OutputStream 的底层实现。 ByteArrayOutputStream 肯定会将它全部缓冲在内存中(并且调用 flush() 不会做任何事情),但 FileOutputStream 将管理它自己的内部缓冲,并且应该信任它可以按照它认为合适的方式去做。不必要地调用 flush() 会影响实现,并可能破坏内部缓冲带来的任何性能提升。【参考方案2】:

Android 应用程序的堆内存有限,并且取决于设备。目前大多数新设备都有 64 个,但它可能或多或少取决于制造商。我见过设备带有 128 MB 堆内存。

那么这到底是什么意思?

这只是意味着无论可用的物理内存如何,您的应用程序都不允许增长超过分配的堆大小。

从 Android API 级别 11 开始,您可以使用清单标记 android:largeHeap="true" 请求额外的内存,这将使您的堆大小增加一倍。这只是意味着如果您的设备有 64 个,您将获得 128 个,如果是 128 个,您将获得 256 个。但这不适用于较低的 API 版本。

我不确定您的要求是什么,但如果您打算通过 HTTP 发送,则读取文件发送数据并再次读取。您也可以对文件 IO 遵循相同的过程。只是为了确保不要使用超过可用堆大小的内存。只是要格外小心,请确保为应用程序执行留出一些空间。

【讨论】:

【参考方案3】:

您的问题不在于如何将 InputStream 转换为字节数组,而是该数组太大而无法放入内存。您别无选择,只能找到一种方法以较小的块处理来自 InputStream 的字节。

【讨论】:

【参考方案4】:

您可能需要大量增加堆大小。尝试使用-Xms384m -Xmx384m 标志运行您的Java 虚拟机(它指定384 兆字节的起始和最大堆大小,除非我错了)。有关可用选项的旧版本,请参阅 this:根据特定的虚拟机和平台,您可能需要进行一些挖掘,但 -Xms 和 -Xmx 应该可以帮助您克服困难。

现在,您可能真的不应该将它读入字节数组,但如果那是您的应用程序,那么...

【讨论】:

你有安卓手机吗?你知道开机后有多少可用内存吗? 这是运行 Dalvik VM 的 Android,而不是 JVM。用户无法设置启动选项,如堆大小。 啊..错过了标签:我的错误。是的,这对他来说永远都行不通:Android 应用程序的堆大小通常限制在 64 MB 以下,即使在高端也是如此。 android:largeHeap="true" 可能会让他在最近的设备上取得成功(如 ***.com/questions/5350465/… 所述),但总的来说这是不可能的。【参考方案5】:

试试这个代码

private byte[] convertStreamToByteArray(InputStream inputStream) 
ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
                    int readByte = 0;
        byte[] buffer = new byte[2024];

                    while(true)
                    
                        readByte = inputStream.read(buffer);
                        if(readByte == -1)
                        
                            break;
                        
                        byteOutStream.write(buffer);
                    
                    inputStream.close();
                    byteOutStream.flush();
                    byteOutStream.close();
                    byte[] byteArray= byteOutStream.toByteArray();
                    return byteArray;

尝试从 InputStream 读取数据块。

【讨论】:

这对于内存不足错误绝对没有帮助。如果有的话,使用 2K 缓冲区会使问题变得更糟。 -1 表示代码中的 OuyOfMemory 异常。您的代码应包含“byteOutStream.flush();”在 while 循环内,或者您想将 200MB 加载到堆中 绝对不需要调用flush()。正如 [user_half] 所说,这实际上会更糟。最重要的是,您还将 complete 缓冲区写入输出流,即使它只是部分填充。所以你要破坏流。您应该调用 byteOutStream.write(buffer, 0, readByte);最后,readByte 实际上并不是一个字节,它是一个 int,表示读取的字节数,所以大多数人称之为“读取”。你的名字表明它是读取的字节,而不是计数。

以上是关于如何在没有 OutOfMemory 错误的情况下从 FileInputStream 获取字节数组的主要内容,如果未能解决你的问题,请参考以下文章

如何在没有用户身份验证的情况下从 Spotify 或任何其他 API 获取歌曲预览

如何在没有 instagram API 的情况下从 instagram 获取公共用户的所有帖子

如何在没有开发人员帐户的情况下从 .app 文件构建 .ipa 文件?

如何在没有 \n 的情况下从 txt 中获取特定行(Python)

如何在没有“可选”的情况下从 plist 打印字符串?

如何在没有终端提示的情况下从 IPython 会话中复制