如何在不溢出堆的情况下处理大型(70MB未压缩)字节流的解压缩?

时间:2013-05-30 19:03:29

标签: java heap-memory gzipinputstream

我正在为一些系统之间的交互实施GZIP压缩。这些系统用Java和C#编写,因此GZIP流用于双方,因为它们具有标准的库支持。

在C#方面,一切都可以解决并包括我们最大的测试文件(70MB未压缩),但是我们遇到了Java用尽堆空间的问题。我们已经尝试将堆大小增加到IDE的容量,但问题仍未解决。

我已经采取了一些措施来尝试优化Java代码,但似乎没有任何东西可以防止数据堆积在堆中。有没有一个好方法来处理这个?下面是我当前(处理较小的流)解决方案的一个子集。

编辑:以下代码经过@MarkoTopolnik的推荐修改。通过更改,在崩溃之前会读取1700万个字符。

public static String decompress(byte[] compressed, int size)
{
    GZIPInputStream decompresser;
    BufferedReader reader;
    char buf[] = new char[(size < 2048) ? size : 2048];
    Writer ret = new StringWriter( buf.length );

    decompresser = new GZIPInputStream( new ByteArrayInputStream( compressed ), buf.length );
    reader = new BufferedReader( new InputStreamReader( decompresser, "UTF-8" ) );

    int charsRead;
    while( (charsRead = reader.read( buf, 0, buf.length )) != -1 )
    {
        ret.write( buf, 0, charsRead );
    }
    decompresser.close();
    reader.close();

    return ret.toString();
}

代码在ArrayList中点击超过760万个字符后死亡,堆栈跟踪表明ArrayList.add()调用是原因(触发内部数组扩展后失败) )。

使用上面编辑过的代码,调用AbstractStringBuilder.expandCapacity()就会杀死程序。

实现动态数组的内存成本是否较低,或者我可以使用一些完全不同的方法从解压缩的流中获取字符串?任何建议将不胜感激!

2 个答案:

答案 0 :(得分:3)

我把它整块而不是将整个内容读入内存:一次读入一个1024字节的缓冲区并立即将其写出来,更像是一个Unix管道而不是两步读/写过程。

答案 1 :(得分:3)

哦,是的,有更有效的方法。代码中最明显的低效率是您创建了ArrayList<Character>。这意味着每个字符占用大约30个字节的内存。乘以你的760万,它是250 MB。

您必须使用的是StringWriter及其方法write(char[],int,int),您可以使用您已有的相同缓冲区调用它。这将是内存效率的25倍左右。