GZIP解压缩C#OutOfMemory

Wil*_*vin 6 c# compression gzip out-of-memory gzipstream

我有很多大的gzip文件(大约10MB - 200MB),我从ftp下载解压缩.

所以我试着google并找到一些gzip解压缩的解决方案.

    static byte[] Decompress(byte[] gzip)
    {
        using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
        {
            const int size = 4096;
            byte[] buffer = new byte[size];
            using (MemoryStream memory = new MemoryStream())
            {
                int count = 0;
                do
                {
                    count = stream.Read(buffer, 0, size);
                    if (count > 0)
                    {
                        memory.Write(buffer, 0, count);
                    }
                }
                while (count > 0);
                return memory.ToArray();
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

它适用于50mb以下的任何文件但是一旦我输入超过50mb我得到系统内存异常.异常之前的最后位置和内存长度是134217728.我不认为它与我的物理内存有关系,我知道我使用32位时不能有超过2GB的对象.

我还需要在解压缩文件后处理数据.我不确定内存流是否是最好的方法,但我不喜欢写入文件,然后再次读取文件.

我的问题

  • 为什么我得到System.OutMemoryException?
  • 什么是解压缩gzip文件并在之后进行一些文本处理的最佳解决方案?

Ale*_*kov 4

MemoryStream的内存分配策略对于海量数据并不友好。

由于 MemoryStream 的约定是使用连续数组作为底层存储,因此它必须经常为大流重新分配数组(通常为 log2(size_of_stream))。这种重新分配的副作用是

  • 重新分配时的长时间复制延迟
  • 新数组必须适合已被先前分配严重碎片化的空闲地址空间
  • 新数组将位于有其怪癖的 LOH 堆上(没有压缩,在 GC2 上收集)。

因此,通过 MemoryStream 处理大型 (100Mb+) 流可能会在 x86 系统上出现内存不足异常。此外,返回数据的最常见模式是像您一样调用 GetArray,这还需要与用于 MemoryStream 的最后一个数组缓冲区大约相同的空间量。

解决办法:

  • 最便宜的方法是预先将 MemoryStream 增长到您需要的近似大小(最好稍微大一些)。您可以通过读取不存储任何内容的假流来预先计算所需的大小(浪费 CPU 资源,但您将能够读取它)。还可以考虑返回流而不是字节数组(或返回 MemoryStream 缓冲区的字节数组以及长度)。
  • 如果您需要整个流或字节数组,处理它的另一个选择是使用临时文件流而不是 MemoryStream 来存储大量数据。
  • 更复杂的方法是实现将底层数据分块为较小(即 64K)块的流,以避免在 LOH 上分配并在流需要增长时复制数据。