2

我正在为我们的一些系统之间的交互实施 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();
}

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

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

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

4

2 回答 2

3

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

于 2013-05-30T19:06:51.293 回答
3

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

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

于 2013-05-30T19:24:59.893 回答