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

Ars*_*ray 5 c# gzip 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");
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

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

        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();
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

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

我该如何阅读第二个流?

Jac*_*cob 5

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

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

    const int Id1 = 0x1F;
    const int Id2 = 0x8B;
    const int DeflateCompression = 0x8;
    const int GzipFooterLength = 8;
    const int MaxGzipFlag = 32; 

    /// <summary>
    /// Returns true if the stream could be a valid gzip header at the current position.
    /// </summary>
    /// <param name="stream">The stream to check.</param>
    /// <returns>Returns true if the stream could be a valid gzip header at the current position.</returns>
    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;
    }
Run Code Online (Sandbox Code Playgroud)

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

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


Mar*_*ler 5

这是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很有效.