【问题标题】:.NET GZipStream decompress producing empty stream.NET GZipStream 解压缩产生空流
【发布时间】:2012-08-11 14:41:00
【问题描述】:

我正在尝试序列化和压缩 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

为什么最终的uncompressed 流不包含原始的 123 个字节?

(请忽略“压缩”字节数组大于“未压缩”字节数组的事实——我通常会处理更大的流文档)

【问题讨论】:

标签: c# .net wpf compression gzipstream


【解决方案1】:

在从内存流中获取压缩字节之前,您需要关闭GZipStream。在这种情况下,关闭由由于使用而调用的Dispose 处理。

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 处理,除非您使用允许您指定底层流应保持打开状态的构造函数重载。这意味着您在已处置的流上调用 ToArray 的代码,但这是允许的,因为字节仍然可用,这使得处置内存流有点奇怪,但如果您不这样做,FXCop 会惹恼您。

【讨论】:

  • “在从内存流中获取压缩字节之前,您需要关闭GZipStream” - 为什么?如果在关闭之前和之后调用 .ToArray(),为什么会得到不同数量的字节?
  • 除了输出以块的形式写入之外,GZipStream 在压缩数据之前添加了页眉,在之后添加了页脚。页脚只能在您关闭流时添加。
【解决方案2】:

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;。

【讨论】:

  • 放气压缩器(根据 gzip 格式的要求)以块的形式生成压缩输出。压缩器需要累积数据以构建块并在发出之前对其进行统计。当您输入结束时,您需要告诉 deflator 完成最后一个块并将其发送出去。否则,这些数据将坐在那里等待更多数据填充一个块。
  • 顺便说一句,压缩数据大小超过未压缩数据大小的数量是 GZipStream 中的几个错误之一。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多