...但我发现在复制操作期间的某个时刻大约需要 160MB 的堆空间
我觉得这非常令人惊讶……在某种程度上,我怀疑您是否正确测量了堆使用情况。
假设您的代码是这样的:
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("somefile"));
ByteArrayOutputStream baos = new ByteArrayOutputStream(); /* no hint !! */
int b;
while ((b = bis.read()) != -1) {
baos.write((byte) b);
}
byte[] stuff = baos.toByteArray();
现在 ByteArrayOutputStream 管理其缓冲区的方式是分配一个初始大小,并且(至少)在缓冲区填满时将缓冲区加倍。因此,在最坏的情况下,baos
可能会使用多达 80Mb 的缓冲区来保存 40Mb 的文件。
baos.size()
最后一步分配一个新的字节数组来保存缓冲区的内容。那是40Mb。所以实际使用的内存峰值应该是120Mb。
那么这些额外的 40Mb 用在了哪里呢?我的猜测是它们不是,并且您实际上是在报告总堆大小,而不是可访问对象占用的内存量。
那么解决方案是什么?
您可以使用内存映射缓冲区。
您可以在分配ByteArrayOutputStream
;时给出大小提示 例如
ByteArrayOutputStream baos = ByteArrayOutputStream(file.size());
您可以完全放弃ByteArrayOutputStream
并直接读入字节数组。
byte[] buffer = new byte[file.size()];
FileInputStream fis = new FileInputStream(file);
int nosRead = fis.read(buffer);
/* check that nosRead == buffer.length and repeat if necessary */
选项 1 和 2 在读取 40Mb 文件时的峰值内存使用量应为 40Mb;即没有浪费空间。
如果您发布您的代码并描述您测量内存使用情况的方法,将会很有帮助。
我想我可以扩展 ByteArrayOutputStream 并重写这个方法,所以直接返回原始数组。考虑到流和字节数组不会被多次使用,这里是否有任何潜在的危险?
潜在的危险是您的假设不正确,或者由于其他人无意中修改了您的代码而变得不正确......