解压缩字节时抛出System.OutOfMemory异常

时间:2016-07-12 06:56:03

标签: c# out-of-memory deflate

我正在压缩字节,并在解压缩时再次出现OOM异常。当我有足够的内存来存储它时,我无法理解为什么会出现此错误。

要解压缩后的数据大约为20MB。但我总是得到OutOfMemory异常。

以下是相同的代码。

public byte[] Compress(byte[] data)
{
    byte[] compressArray = null;
    try
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress))
            {
                deflateStream.Write(data, 0, data.Length);
                deflateStream.Close();
            }
            compressArray = memoryStream.GetBuffer();
            memoryStream.Dispose();
        }
    }
    catch (Exception exception)
    {
        LogManager.LogEvent(EventLogEntryType.Error, exception.Message);
        return data;
    }
    finally { GC.Collect(); }
    return compressArray;
}

public static byte[] Decompress_Bytes(byte[] data)// Around 20MB data
{
    byte[] decompressedArray = null;
    try
    {
        using (MemoryStream decompressedStream = new MemoryStream())
        {
            using (MemoryStream compressStream = new MemoryStream(data))
            {
                using (DeflateStream deflateStream = new DeflateStream(compressStream, CompressionMode.Decompress))
                {
                    deflateStream.CopyTo(decompressedStream);// Exception thrown at this line.
                    deflateStream.Close();
                }
                compressStream.Dispose();
            }
            decompressedArray = decompressedStream.GetBuffer();
            decompressedStream.Dispose();
        }
    }
    catch (Exception exception)
    {
        return data;
    }
    finally { GC.Collect(); }

    return decompressedArray;
}

下面是堆栈跟踪,以便更好地理解。

at System.IO.MemoryStream.set_Capacity(Int32 value)
at System.IO.MemoryStream.EnsureCapacity(Int32 value)
at System.IO.MemoryStream.Write(Byte[] buffer, Int32 offset, Int32 count)
at System.IO.Stream.InternalCopyTo(Stream destination, Int32 bufferSize)
at System.IO.Stream.CopyTo(Stream destination)
at Symtrax.SQConsole.ConsoleConnectClass.Decompress_Bytes(Byte[] data) in c:\Developement\BI\branch_5.0\MapDesignerUNICODE\ConsoleConnector\SQConsole\ConsoleConnectClass.cs:line 3710

我发现了许多关于此的相关问题,但它们似乎都没有解决我的问题。

由于我的声望点较少,我无法发表评论。因此不得不提出问题。提前谢谢。

1 个答案:

答案 0 :(得分:3)

正如评论中所述,您正在获取具有GetBuffer的内部缓冲区,该缓冲区具有不同的长度特征,然后只需调用ToArray

我在您的代码中添加了一些转储语句,因此LINQPad可以揭示正在发生的事情:

public byte[] Compress(byte[] data)
{
    byte[] compressArray = null;
    data.Length.Dump("initial array length");
    try
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress))
            {
                deflateStream.Write(data, 0, data.Length);
                deflateStream.Close();
            }
            memoryStream.GetBuffer().Length.Dump("buffer compress len");
            compressArray = memoryStream.ToArray();
            compressArray.Length.Dump("compress array len");
            // no need to call Dispose, using does that for you
            //memoryStream.Dispose();
        }
    }
    catch (Exception exception)
    {
        exception.Dump();
        return data;
    }
    finally { GC.Collect(); }
    return compressArray;
}

public static byte[] Decompress_Bytes(byte[] data)// Around 20MB data
{
    byte[] decompressedArray = null;
    try
    {
        using (MemoryStream decompressedStream = new MemoryStream())
        {
            using (MemoryStream compressStream = new MemoryStream(data))
            {
                using (DeflateStream deflateStream = new DeflateStream(compressStream, CompressionMode.Decompress))
                {
                    deflateStream.CopyTo(decompressedStream);// Exception thrown at this line.
                    deflateStream.Close();
                }
                // no need, using does that
                //compressStream.Dispose();
            }
            decompressedStream.GetBuffer().Length.Dump("buffer decompress len");
            decompressedArray = decompressedStream.ToArray();
            decompressedArray.Length.Dump("decompress array len");
            // no need, using does that
            decompressedStream.Dispose();
        }
    }
    catch (Exception exception)
    {
        exception.Dump();
        return data;
    }
    finally { GC.Collect(); }

    return decompressedArray;
}

这是输出:

初始数组长度 248404

缓冲压缩len 262144

压缩数组len 189849

缓冲区解压缩len 327680

解压缩数组len 248404

从这些数字中可以看出,你的长度数量会有很多不同。如果Deflate协议允许具有额外字节的字节流,则可以使用那些额外的字节。

使用GetBuffer而不是ToArray似乎是有益的,但我希望复制最终阵列所需的内存分配和CPU滴答是可以忽略的,特别是如果内存流无论如何都要处理的话。这个事实实际上减少了内存占用。

如果仍然坚持重新使用内存流缓冲区,请确保还返回并提供缓冲区中的实际长度:

public byte[] Compress(byte[] data, out int len)
{
    byte[] compressArray = null;
    data.Length.Dump("initial array length");
    try
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            // keep the stream open, we need the length!
            using (DeflateStream deflateStream = new DeflateStream(
                                       memoryStream,
                                       CompressionMode.Compress, 
                                       true))
            {
                deflateStream.Write(data, 0, data.Length);
                deflateStream.Close();
            }
            // output length
            len = (int) memoryStream.Length;                
            compressArray = memoryStream.GetBuffer();
        }
    }
    catch (Exception exception)
    {
        exception.Dump();
        len =-1;
        return data;
    }
    finally { GC.Collect(); }
    return compressArray;
}

public static byte[] Decompress_Bytes(byte[] data, ref int len)// Around 20MB data
{
    byte[] decompressedArray = null;
    try
    {
        using (MemoryStream decompressedStream = new MemoryStream())
        {
            // use the overload that let us limit the memorystream buffer
            using (MemoryStream compressStream = new MemoryStream(data,0, len))
            {
                // keep the stream open
                using (DeflateStream deflateStream = new DeflateStream(
                                compressStream, 
                                CompressionMode.Decompress, 
                                true))
                {
                    deflateStream.CopyTo(decompressedStream);// Exception thrown at this line.
                    deflateStream.Close();
                }
            }
            // output length
            decompressedArray = decompressedStream.GetBuffer();
            len = (int) decompressedStream.Length;
        }
    }
    catch (Exception exception)
    {
        exception.Dump();
        return data;
    }
    finally { GC.Collect(); }

    return decompressedArray;
}

如果您使用上面的代码,您可以这样称呼它:

int len;
var cmp = Compress(Encoding.UTF8.GetBytes(sb.ToString()), out len);
var dec = Decompress_Bytes(cmp,ref len);

请注意,要使用dec中的字节,您只需考虑第一个len字节数。实际上这是通过使用Array.Copy来完成的,这会使这个解决方案失败,并将我们带回到调用ToArray的那个......