【问题标题】:GZIP decompression C# OutOfMemoryGZIP解压C# OutOfMemory
【发布时间】:2012-05-03 00:58:39
【问题描述】:

我有许多从 ftp 下载的大型 gzip 文件(大约 10MB - 200MB)要解压缩。

所以我尝试谷歌并找到一些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();
            }
        }
    }

它适用于任何低于 50mb 的文件,但是一旦我输入超过 50mb 的文件,就会出现系统内存不足异常。异常前的最后位置和内存长度是 134217728。我认为它与我的物理内存无关,我知道我使用 32 位,所以我不能拥有超过 2GB 的对象。

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

我的问题

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

【问题讨论】:

  • 您正在将流的全部内容加载到内存中并将其作为字节数组返回。除了内存不足的异常,您还期望 other 什么?您不应该像这样将它全部加载到内存中——您最终打算对数组做什么?将其写入文件?无论你想要什么,它都应该是基于流的,而不是基于数组的。
  • 好吧.. memory.write 发生异常并卡在 134217728 中.. 我不熟悉内存管理,所以请多多包涵。稍后我会将所有处理的文件保存到数据库中,gzipped文件中的文件是csv文件
  • 当然,但是如果您在解压缩的同时处理它,您的设计会更好。这样您就不必分配大量内存来处理它。 (例如,通过将 gzip 流直接放入 StreamReader
  • 在你的函数原型中最容易发现错误:static byte[] Decompress(byte[] gzip)。您想将 stream 作为参数,而不是数组。
  • 感谢您的建议。我会尝试使用流。

标签: c# gzip out-of-memory compression gzipstream


【解决方案1】:

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

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

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

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

解决方法:

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

【讨论】:

  • 是的,感谢您向我澄清这一点。我现在有点明白了,在这种情况下,内存流对我来说不是好朋友。我认为它可以帮助提高性能,但反而让我更加头疼。谢谢
【解决方案2】:

您可以尝试如下测试,以了解在发生 OutOfMemoryException 之前您可以向 MemoryStream 写入多少数据:

        const int bufferSize = 4096;
        byte[] buffer = new byte[bufferSize];

        int fileSize = 1000 * 1024 * 1024;

        int total = 0;

        try
        {
            using (MemoryStream memory = new MemoryStream())
            {
                while (total < fileSize)
                {
                    memory.Write(buffer, 0, bufferSize);
                    total += bufferSize;
                }

            }

            MessageBox.Show("No errors"); 

        }
        catch (OutOfMemoryException)
        {
            MessageBox.Show("OutOfMemory around size : " + (total / (1024m * 1024.0m)) + "MB" ); 
        }

您可能必须先解压缩到一个临时的物理文件,然后以小块的形式重新读取它,然后再进行处理。

Side Point:有趣的是,在 Windows XP PC 上,上面的代码给出:当代码针对 .net 2.0 时,“OutOfMemory 大小约为 256MB”,而在 .net 4 上,“OutOfMemory 大小约为 512MB”。

【讨论】:

  • 我已经在上面指定了。如果我正确的话,它会卡在大约 128MB 的 134217728 上。我不知道为什么这发生得太早了,但我想选择内存流是我的第一个错误。谢谢你的回答
  • 可以确认我已经达到了完全相同的限制。
【解决方案3】:

您是否碰巧在多个线程中处理文件?这会消耗大量的地址空间。 OutOfMemory 错误通常与物理内存无关,因此 MemoryStream 的用完可能比您预期的要早得多。检查此讨论http://social.msdn.microsoft.com/Forums/en-AU/csharpgeneral/thread/1af59645-cdef-46a9-9eb1-616661babf90。如果您切换到 64 位进程,那么您处理的文件大小可能会更好。

不过,在您目前的情况下,您可以使用内存映射文件来绕过任何地址大小限制。如果您使用的是 .NET 4.0,它会为 Windows 函数 http://msdn.microsoft.com/en-us/library/dd267535.aspx 提供本机包装器。

【讨论】:

  • 是的,我在 SO 中询问之前看到了该链接。我只想知道我还有什么其他选择。谢谢你的回答
【解决方案4】:

我知道我的对象不能超过 2GB,因为我使用的是 32 位

这是不正确的。您可以根据需要拥有尽可能多的内存。 32 位限制意味着您只能拥有 4GB(操作系统占用一半)的虚拟地址空间。虚拟地址空间不是内存。 Here 读起来不错。

为什么我会得到 System.OutMemoryException?

因为分配器无法为您的对象找到连续的地址空间,或者它发生得太快而阻塞。 (很可能是第一个)

解压缩 gzip 文件的最佳解决方案是什么? 之后进行一些文本处理?

编写一个下载文件的脚本,然后使用 gzip 或 7zip 等工具对其进行解压缩,然后进行处理。根据处理类型、文件数量和总大小,您必须在某些时候保存它们以避免此类内存问题。解压后保存,一次处理1MB。

【讨论】:

猜你喜欢
  • 2014-10-21
  • 2012-06-26
  • 1970-01-01
  • 2017-09-27
  • 1970-01-01
  • 1970-01-01
  • 2017-10-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多