如何在没有 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 文件?