GZIP压缩C#内存不足(GZIP decompression C# OutOfMemory)

2019-06-24 03:16发布

我有很多大的gzip文件(10MB左右 - 200MB),我从FTP下载进行解压缩。

所以我想谷歌和找到的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.我不认为它有关系,与我的物理内存,我明白,我不能有对象超过2GB,因为我使用32位。

我还需要对数据进行处理后解压缩的文件。 我不知道如果内存流是这里最好的办法,但我真的不喜欢写文件,然后再次读取文件。

我的问题

  • 为什么我得到System.OutMemoryException?
  • 什么是最佳的解决方案,以解压gzip文件和事后做一些文字处理?

Answer 1:

对于MemoryStream的内存分配策略是不适合大量数据的友好。

由于用于MemoryStream的合同是具有连续的数组作为底层存储它必须重新分配阵列往往不够用于大型流(通常为log2(size_of_stream))。 这种再分配的副作用

  • 在再分配长拷贝延误
  • 新的数组必须符合自由地址空间已经由以前的拨款大量碎片
  • 新数组将是有它的怪癖(没有压实,收集上GC2)LOH堆。

作为结果通过的MemoryStream x86系统处理大量(100Mb的+)流将有可能的情况下进行存储器的异常。 除了最常见的模式返回数据是调用的getArray你做这还需要有关用于MemoryStream的相同数量的空间作为最后的数组缓冲区。

方法来解决:

  • 最便宜的方式是预先成长的MemoryStream到你需要(最好是稍微大)近似大小。 您可以预先计算是通过读取到不存储任何虚假流所需尺寸(CPU资源的浪费,但你能够阅读它)。 考虑也返回流,而不是字节阵列(或具有长度沿返回的MemoryStream缓冲区的字节数组)。
  • 如果您需要整个流或字节数组来处理它的另一种选择是使用临时文件流,而不是MemoryStream的存储大量数据。
  • 更复杂的方法是实现该组块中更小的(即64K)块底层数据,以避免对LOH和复制数据分配时流需要成长流。


Answer 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" ); 
        }

您可能需要先解压缩到一个临时的物理文件,当你走在小块重新阅读和处理。

侧点:有趣的是,在Windows XP的PC上,上面的代码给出了:“各地的大小256MB内存不足”当代码面向.NET 2.0,和“内存溢出各地大小512MB”关于.NET 4。



Answer 3:

你恰巧在多线程处理的文件? 这将消耗大量的地址空间。 内存溢出错误通常是不相关的物理内存,所以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 。



Answer 4:

我明白,我不能有对象超过2GB,因为我用32位

这是不正确。 因为您可以根据需要有尽可能多的内存。 32位的限制意味着你只能有4GB(OS花费的一半)的虚拟地址空间。 虚拟地址空间是不是内存。 这里是一个很好的阅读。

为什么我得到System.OutMemoryException?

由于分配器无法找到你的目标相邻的地址空间,或者它发生得太快,它堵塞。 (最有可能第一个)

什么是最佳的解决方案,以解压gzip文件和事后做一些文字处理?

编写下载文件的脚本,然后使用gzip的一样或7zip的工具将其解压缩,然后进行处理。 根据种类的处理,文件和总规模的数字,你将不得不将它们保存在某个时刻避免这种内存问题。 在一次unziping和工艺1MB后保存。



文章来源: GZIP decompression C# OutOfMemory