防止GZipStream / DeflateStream尝试使用比压缩数据更多的内容

时间:2015-03-11 03:21:57

标签: .net stream compression gzipstream deflatestream

我有一个本可以创建的文件:

stream.Write(headerBytes, 0, headerBytes.Count);

using (var gz = new GZipStream(stream, Compress, leaveOpen: true);
{
    gz.Write(otherBytes, 0, otherBytes.Count);
}

stream.Write(moreBytes, 0, moreBytes.Count);

现在读取文件

stream.Read(headerBytes, 0, headerBytes.Count);
// in reality I make sure that indeed headerBytes.Count get read,
// something the above line omits

using (var gz = new GZipStream(stream, Decompress, leaveOpen: true)
{
  do { /* use buffer... */}
  while ((bytesRead = gz.Read(buffer, 0, buffer.Length)) != 0);
}

while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
  // use buffer...

事实证明GZipStream(同样适用于DeflateStream)从stream读取16384个字节,而不是我检查的情况下的实际13293个压缩字节。

假设我既不知道文件压缩部分的大小,也不知道压缩数据后面的字节数,有没有办法使用GzipStream / DeflateStream

  1. 所以它只从stream
  2. 读取压缩数据
  3. 或者至少弄清楚压缩数据部分的大小,我可以手动stream.Position -= actuallyRead - compressedSize吗?

3 个答案:

答案 0 :(得分:1)

该界面似乎没有提供你想要的方法,one of many reasons不使用.NET的GZipStream或DeflateStream。

您应该使用DotNetZip代替。

答案 1 :(得分:0)

此答案相当于丑陋的解决方法。我不是特别喜欢它,但它确实有效(除非它没有),即使只是GZipStream

  
      
  1. 或者至少弄清楚压缩数据部分的大小,我可以stream.Position -= actuallyRead - compressedSize   手动?
  2.   

作为每个gzip文件(实际上每个 gzip成员ends with

     +---+---+---+---+---+---+---+---+
     |     CRC32     |     ISIZE     |
     +---+---+---+---+---+---+---+---+

     CRC32
        This contains a Cyclic Redundancy Check value of the
        uncompressed data

     ISIZE
        This contains the size of the original (uncompressed) input
        data modulo 2^32.

我可以使用未压缩的大小(模块2 ^ 32),我在关闭GzipStream后知道,然后在流中向后搜索,直到找到与之匹配的4个字节。

为了使其更加健壮,我还应该在解压缩时计算CRC32,并在8个字节形成正确的CRC32和ISIZE之后在流中向后搜索。

丑陋,但我确实警告过你。

< sarcasm>我如何爱封装。将所有有用的东西封装起来,留给我们一个解压缩的Stream,它完全适用于所有知道的API设计师预见的一个用例。< / sarcasm>

这是一个迄今为止有效的快速SeekBack实现:

/// <returns>the number of bytes sought back (including bytes.Length)
///          or 0 in case of failure</returns>
static int SeekBack(Stream s, byte[] bytes, int maxSeekBack)
{
    if (maxSeekBack != -1 && maxSeekBack < bytes.Length)
        throw new ArgumentException("maxSeekBack must be >= bytes.Length");

    int soughtBack = 0;
    for (int i = bytes.Length - 1; i >= 0; i--)
    {
        while ((maxSeekBack == -1 || soughtBack < maxSeekBack)
               && s.Position > i)
        {
            s.Position -= 1;
            // as we are seeking back, the following will never become
            // -1 (EOS), so coercing to byte is OK
            byte b = (byte)s.ReadByte();
            s.Position -= 1;
            soughtBack++;
            if (b == bytes[i])
            {
                if (i == 0)
                    return soughtBack;
                break;
            }
            else
            {
                var bytesIn = (bytes.Length - 1) - i;
                if (bytesIn > 0) // back to square one
                {
                    soughtBack -= bytesIn;
                    s.Position += bytesIn;
                    i = bytes.Length - 1;
                }
            }
        }
    }
    // no luck? return to original position
    s.Position += soughtBack;
    return 0;
}

答案 2 :(得分:0)

根据Mark Adler的建议,我尝试DotNetZip,并且看,它的GZipStream.Position属性不仅不会抛出,它甚至会返回读入的实际gzip字节数(加8,for某些原因,我仍然需要弄清楚。)

所以它读取的内容超出了严格要求,但它可以让我计算回溯量。

以下适用于我:

var posBefore = fileStream.Position;
long compressedBytesRead;
using (var gz = new GZipStream(fileStream, CompressionMode.Decompress, true))
{
    while (gz.Read(buffer, 0, buffer.Length) != 0)
        ; // use it!
    compressedBytesRead = gz.Position;
}
var gzipStreamAdvance = fileStream.Position - posBefore;
var seekBack = gzipStreamAdvance - compressedBytesRead - 8; // but why "- 8"?
fileStream.Position -= seekBack;