.NET GZipStream解压缩产生空流

12

我尝试序列化和压缩一个WPF FlowDocument,然后做相反的操作 - 解压缩字节数组并反序列化以重新创建FlowDocument - 使用 .NET 的GZipStream类。我遵循MSDN上描述的示例,并有以下测试程序:

var flowDocumentIn = new FlowDocument();
flowDocumentIn.Blocks.Add(new Paragraph(new Run("Hello")));
Debug.WriteLine("Compress");
byte[] compressedData;
using (var uncompressed = new MemoryStream())
{
    XamlWriter.Save(flowDocumentIn, uncompressed);
    uncompressed.Position = 0;
    using (var compressed = new MemoryStream())
    using (var compressor = new GZipStream(compressed, CompressionMode.Compress))
    {
        Debug.WriteLine(" uncompressed.Length: " + uncompressed.Length);
        uncompressed.CopyTo(compressor);
        Debug.WriteLine(" compressed.Length: " + compressed.Length);
        compressedData = compressed.ToArray();
    }
}

Debug.WriteLine("Decompress");
FlowDocument flowDocumentOut;
using (var compressed = new MemoryStream(compressedData))
using (var uncompressed = new MemoryStream())
using (var decompressor = new GZipStream(compressed, CompressionMode.Decompress))
{
    Debug.WriteLine(" compressed.Length: " + compressed.Length);
    decompressor.CopyTo(uncompressed);
    Debug.WriteLine(" uncompressed.Length: " + uncompressed.Length);
    flowDocumentOut = (FlowDocument) XamlReader.Load(uncompressed);
}

Assert.AreEqual(flowDocumentIn, flowDocumentOut);

然而,在XamlReader.Load行处我会收到异常,这很正常,因为调试输出显示未压缩的流长度为零。

Compress
 uncompressed.Length: 123
 compressed.Length: 202
Decompress
 compressed.Length: 202
 uncompressed.Length: 0
为什么最终的“未压缩”流不包含原始的123个字节?(请忽略“压缩”字节数组比“未压缩”字节数组大的事实——通常我会处理更大的流文档)

虽然您可能会解决这个问题,但是您应该考虑是否首先要使用那个类。请参见我的评论:https://dev59.com/4Ggu5IYBdhLWcg3wTFOi - Mark Adler
3个回答

16

在从内存流中获取压缩字节之前,您需要关闭GZipStream。在这种情况下,关闭操作由Dispose方法处理,因为使用了using

using (var compressed = new MemoryStream())
{
    using (var compressor = new GZipStream(compressed, CompressionMode.Compress))
    {
        uncompressed.CopyTo(compressor);
    }
    // Get the compressed bytes only after closing the GZipStream
    compressedBytes = compressed.ToArray();
}

这个方法是可行的,你甚至可以移除 MemoryStream 上的 using 语句,因为通过 GZipStream 进行处理后,MemoryStream 将被自动释放,除非你使用构造函数重载,并指定基础流应该保持打开状态。这意味着在调用 ToArray 方法时,你是在一个已释放的流上进行操作,但这是允许的,因为字节仍然是可用的,这使得释放内存流有点奇怪,但如果不这样做,FXCop(代码分析工具)会给你带来麻烦。


1
你需要在从内存流获取压缩字节之前关闭 GZipStream 流,为什么呢?为什么在关闭流之前和之后调用 .ToArray() 会得到不同数量的字节呢? - Tom Hunter
5
除了输出以块的形式编写之外,GZipStream 还会在压缩数据之前添加一个头部和之后添加一个尾部。只有在关闭流时才能添加尾部。 - João Angelo

5

Joao的答案解决了问题。我已经复制了完整的工作示例如下。我添加了一行代码以输出compressedData.Length。有趣的是,这将输出218个字节,而compressedStream.Length只输出202个字节。如果在读取字节数组之前不关闭GZipStream,则compressedData.Length为202。我不知道为什么关闭GZipStream会给你额外的16个字节。

var flowDocumentIn = new FlowDocument();
flowDocumentIn.Blocks.Add(new Paragraph(new Run("Hello")));

Debug.WriteLine("Compress");

byte[] compressedData;

using (var uncompressedStream = new MemoryStream())
{
    XamlWriter.Save(flowDocumentIn, uncompressedStream);
    uncompressedStream.Position = 0;
    using (var compressedStream = new MemoryStream())
    {
        using (var gZipCompressor = new GZipStream(compressedStream, CompressionMode.Compress))
        {
            Debug.WriteLine(" uncompressedStream.Length: " + uncompressedStream.Length);
            uncompressedStream.CopyTo(gZipCompressor);
            Debug.WriteLine(" compressedStream.Length: " + compressedStream.Length);
        }
        compressedData = compressedStream.ToArray();
    }
}

Debug.WriteLine(" compressedData.Length: " + compressedData.Length);

Debug.WriteLine("Decompress");

FlowDocument flowDocumentOut;

using (var compressedStream = new MemoryStream(compressedData))
using (var uncompressedStream = new MemoryStream())
{
    using (var gZipDecompressor = new GZipStream(compressedStream, CompressionMode.Decompress))
    {
        Debug.WriteLine(" compressedStream.Length: " + compressedStream.Length);
        gZipDecompressor.CopyTo(uncompressedStream);
        Debug.WriteLine(" uncompressedStream.Length: " + uncompressedStream.Length);
    }
    uncompressedStream.Position = 0;
    flowDocumentOut = (FlowDocument)XamlReader.Load(uncompressedStream);
}

调试输出:

Compress
 uncompressedStream.Length: 123
 compressedStream.Length: 202
 compressedData.Length: 218
Decompress
 compressedStream.Length: 218
 uncompressedStream.Length: 123

请注意,在调用XamlReader.Load之前,还需要添加uncompressedStream.Position = 0;

3
一个用于gzip格式的压缩程序(即deflate压缩器)会将输出压缩为块。该压缩程序需要累积数据以建立一个块并在发出之前对其进行统计分析。当你到达输入的末尾时,你需要告诉压缩程序完成最后一个块并将其发送出去。否则这些数据就会一直等待更多数据来填满一个块。 - Mark Adler
1
顺便提一下,压缩数据大小超过未压缩数据大小的数量是GZipStream中几个错误之一。 - Mark Adler

0

在将解压缩的字节复制到流中后,您需要将其位置设置为零,以便正确读取它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接