如何从包含多个GzipStream的文件中读取

我有一个用代码创建的文件,如下所示:

using (var fs=File.OpenWrite("tmp")) { using (GZipStream gs=new GZipStream(fs,CompressionMode.Compress,true)) { using (StreamWriter sw=new StreamWriter(gs)) { sw.WriteLine("hello "); } } using (GZipStream gs = new GZipStream(fs, CompressionMode.Compress, true)) { using (StreamWriter sw = new StreamWriter(gs)) { sw.WriteLine("world"); } } } 

现在我正在尝试使用以下代码从此文件中读取数据:

  string txt; using (var fs=File.OpenRead("tmp")) { using (GZipStream gs=new GZipStream(fs,CompressionMode.Decompress,true)) { using (var rdr = new StreamReader(gs)) { txt = rdr.ReadToEnd(); } } using (GZipStream gs = new GZipStream(fs, CompressionMode.Decompress, true)) { using (StreamReader sr = new StreamReader(gs)) { txt+=sr.ReadToEnd(); } } } 

第一个流读取正常,但第二个流不读取。

我该如何阅读第二个流?

这是GzipStream处理具有多个gzip条目的gzip文件的方式的问题。 它读取第一个条目,并将所有后续条目视为垃圾(有趣的是,像gzip和winzip这样的实用程序通过将它们全部解压缩到一个文件中来正确处理它。)有几种解决方法,或者您可以使用第三方实用程序DotNetZip( http://dotnetzip.codeplex.com/ )。

也许最简单的方法是扫描文件中的所有gzip标头,然后手动将流移动到每个标头并解压缩内容。 这可以通过查找原始文件字节中的ID1,ID2和0x8来完成(Deflate压缩方法,请参阅规范: http : //www.gzip.org/zlib/rfc-gzip.html )。 这并不总是足以保证您正在查看gzip标头,因此您需要读取标头的其余部分(或至少前十个字节)以validation:

  const int Id1 = 0x1F; const int Id2 = 0x8B; const int DeflateCompression = 0x8; const int GzipFooterLength = 8; const int MaxGzipFlag = 32; ///  /// Returns true if the stream could be a valid gzip header at the current position. ///  /// The stream to check. /// Returns true if the stream could be a valid gzip header at the current position. public static bool IsHeaderCandidate(Stream stream) { // Read the first ten bytes of the stream byte[] header = new byte[10]; int bytesRead = stream.Read(header, 0, header.Length); stream.Seek(-bytesRead, SeekOrigin.Current); if (bytesRead < header.Length) { return false; } // Check the id tokens and compression algorithm if (header[0] != Id1 || header[1] != Id2 || header[2] != DeflateCompression) { return false; } // Extract the GZIP flags, of which only 5 are allowed (2 pow. 5 = 32) if (header[3] > MaxGzipFlag) { return false; } // Check the extra compression flags, which is either 2 or 4 with the Deflate algorithm if (header[8] != 0x0 && header[8] != 0x2 && header[8] != 0x4) { return false; } return true; } 

请注意,如果直接使用文件流,GzipStream可能会将流移动到文件末尾。 您可能希望将每个部分读入MemoryStream,然后在内存中单独解压缩每个部分。

另一种方法是修改gzip标头以指定内容的长度,这样您就不必扫描文件中的标题(您可以通过编程方式确定每个标题的偏移量),这需要更深入地进入gzip spec。

这是GzipStream中的一个错误。 根据RFC 1952规范的gzip格式 :

2.2。 文件格式

gzip文件由一系列“成员”(压缩数据集)组成。 每个成员的格式在以下部分中指定。 成员只是在文件中一个接一个地出现,在它们之前,之间或之后没有其他信息。

因此,要求兼容的解压缩程序在前一个gzip成员之后立即查找另一个gzip成员。

您应该能够简单地使用一个循环使用错误的GzipStream来读取单个gzip成员,然后再次使用GzipStream从最后一次使用GzipStream的第一个输入字节开始读取下一个gzip成员。 这将是完全可靠的,而不是试图寻找gzip成员的开始的其他建议。

压缩数据可以有任何字节模式,因此当它实际上是gzip成员的压缩数据的一部分时,可能会误以为你找到了一个gzip头。 实际上,其中一种deflate方法是在不压缩的情况下存储数据,在这种情况下,可能会存储在gzip成员中压缩的gzip流(因为大多数数据都是压缩的,因此很可能无法进一步压缩),因此会在gzip成员的压缩数据中间呈现一个完全有效的虚假gzip头。

使用DotNetZip的建议非常好。 GzipStream中有很多错误,其中一些是在.NET 4.5中修复的,有些则显然没有。 微软可能需要几年时间才能弄清楚如何正确编写该类。 DotNetZip很有效。

我和DeflateStream有类似的问题。

一种简单的方法是将您的基础Stream包装在Stream实现中,该实现只在调用Read(byte [] buffer,int offset,int count)时才返回单个字节。 这阻止了DeflateStream / GZipStream的缓冲,当到达第一个流的末尾时,将底层流保持在正确的位置。 当然,由于Read的调用次数增加,这里显然效率低下,但根据您的应用程序,这可能不是问题。

进入DeflateStream的内部,可能会使用reflection来重置内部Inflater实例。

我已经validationSharpZipLib 0.86.0.518可以读取多成员gzip文件:

 using (var fileStream = File.OpenRead(filePath)) using (var gz = new GZipInputStream(fileStream)) { //Read from gz here } 

你可以使用NuGet获得它。