【问题标题】:Can DeflateStream or GZipStream be used to deflate an uncompressed file?可以使用 DeflateStream 或 GZipStream 对未压缩文件进行放气吗?
【发布时间】:2011-04-10 04:25:09
【问题描述】:

我正在尝试对应用程序实施文件压缩。该应用程序已经存在了一段时间,因此它需要能够读取以前版本编写的未压缩文档。我希望 DeflateStream 能够处理未压缩的文件,但对于 GZipStream 我得到“GZip 标头中的幻数不正确”错误。对于 DeflateStream,我得到“解码时发现无效数据”。我猜它没有找到将文件标记为它的类型的标头。

如果不能简单地处理未压缩的文件,那么第二好的方法是确定文件是否被压缩,并选择读取文件的方法。我找到了这个链接:http://blog.somecreativity.com/2008/04/08/how-to-check-if-a-file-is-compressed-in-c/,但这是非常具体的实现,感觉不是正确的方法。它还可以提供误报(我确信这种情况很少见,但它确实表明这不是正确的方法)。

我考虑的第三个选项是尝试使用 DeflateStream,如果发生异常则回退到正常的流 IO。这也让人感觉很混乱,并导致 VS 在异常处中断(除非我取消选中该异常,我真的不想这样做)。

当然,我可能只是走错了路。这是我在 .Net 3.5 中尝试过的代码:

Stream reader = new FileStream(fileName, FileMode.Open, readOnly ? FileAccess.Read : FileAccess.ReadWrite, readOnly ? FileShare.ReadWrite : FileShare.Read);

using (DeflateStream decompressedStream = new DeflateStream(reader, CompressionMode.Decompress))
{
    workspace = (Workspace)new XmlSerializer(typeof(Workspace)).Deserialize(decompressedStream);

    if (readOnly)
    {
        reader.Close();
        workspace.FilePath = fileName;
    }
    else
        workspace.SetOpen(reader, fileName);
}

有什么想法吗?

谢谢! 卢克。

【问题讨论】:

  • 为您的新文件格式使用不同的文件名或扩展名怎么样?
  • 我想这会起作用(作为一个标志),但宁愿避免 if poss,这样最终用户就不必知道他们文档的多个文件扩展名。我也很想知道是否有人有任何其他解决方案,只是想知道这是否是.Net的限制,或者我是否做错了什么。不过谢谢!
  • “试图确定”文件是否被压缩(通过熵、检查非 ascii 字符等)是在自找麻烦。你需要一个合适的文件头。压缩整个文件意味着它不再是 XML 文档(旧版本的应用程序会试图阅读它),所以你没有理由不能添加标题 :)

标签: c# .net compression stream


【解决方案1】:

您的文件格式没有标题吗?如果没有,现在是时候添加一个了(无论如何,您正在通过支持压缩来更改文件格式)。选择一个好的magic value,确保标头是可扩展的(添加版本字段,或为特定版本使用特定的魔法值),然后您就可以开始了。

加载后,检查魔法值。如果不存在,请使用您当前的旧版加载例程。如果存在,标题会告诉你内容是否被压缩。

更新

压缩流意味着 文件 不再是 XML 文档,因此没有太多理由期望 文件 不能包含超过您的数据 。您确实需要一个标头来标识您的文件:)

以下是示例(伪)代码;我不知道 .net 是否有“子流”,SubRangeStream 可能是您必须自己编写的代码(DeflateStream 可能会添加它自己的标头,因此可能不需要子流;可能会在以后变得有用,不过)。

Int64 oldPosition = reader.Position;
reader.Read(magic, 0, magic.length);
if(IsRightMagicValue(magic))
{
    Header header = ReadHeader(reader);
    Stream furtherReader = new SubRangeStream(reader, reader.Position, header.ContentLength); 
    if(header.IsCompressed)
    {
        furtherReader = new DeflateStream(furtherReader, CompressionMode.Decompress);
    }

    XmlSerializer xml = new XmlSerializer(typeof(Workspace));
    workspace = (Workspace) xml.Deserialize(furtherReader); 
} else
{
    reader.Position = oldPosition;
    LegacyLoad(reader);
}

在现实生活中,我会做一些不同的事情——例如,一些适当的错误处理和清理。另外,我不会直接在IsRightMagicValue 块中使用新的加载程序代码,而是我会根据魔法值(每个文件版本一个魔法值)来分拆工作,或者我会保留一个“共同header”部分,其中包含所有版本共有的字段。对于两者,我会使用 Factory Method 来返回 IWorkspaceReader,具体取决于文件版本。

【讨论】:

  • 我正在使用 XML 序列化,它有自己的格式。 (是的,我的 XML 文档中有一个版本号。)它从第一天就开始使用,所以我无法判断文件是否被压缩。这种方法也会受到误报的影响,与我问题中链接中的方法相同。不过谢谢!
  • @Luke:XML 序列化使用 stream,而不是 file。因此,您可以轻松打开 file 并检查标题,然后将 file 的子部分视为 XMLSerializer 的 stream
  • @Luke:查看我的更新以获取更多描述和一些(伪)代码。
  • 我明白你所说的文件和流之间的区别是什么意思。是的,我可以使用带有 XML 序列化的标头。我希望有人会说有一个 DeflateStream.IsCompressedFile() 函数或 File.IsCompressed(),或者告诉我如何将 deflatestream 与未压缩文件一起使用,但我想除了使用我使用的文件的定制标题。感谢您对此的帮助!我会将此标记为正确答案。
  • P.S.关于我对误报的评论:这极不可能,除非我选择我的头来匹配 XML 文件的标准头,这对我来说是粗心的!
【解决方案2】:

你不能创建一个包装类/函数来读取文件并捕获异常吗?类似的东西

try
{
    // Try return decompressed stream 
}
catch(InvalidDataException e)
{
    // Assume it is already decompressed and return it as it is
}

【讨论】:

  • 是的,这是我的问题中描述的选项 3。在我找到更优雅的方法(假设有)之前,我一直在使用这种方法。
  • 猜猜这取决于你对优雅的定义。我发现这个比所有文件头的东西更优雅、更简单。特别是因为你说你已经在使用带有版本号的 XML 编码。
猜你喜欢
  • 1970-01-01
  • 2016-03-15
  • 1970-01-01
  • 1970-01-01
  • 2011-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-12
相关资源
最近更新 更多