防止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...

事实证明,GZipStreamDeflateStream也是如此)从stream读取 16384 字节,而不是我检查的实际 13293 个压缩字节。

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

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

该接口似乎没有提供执行所需操作的方法,这是不使用 的众多原因之一。NET的GZipStream或DeflateStream。

您应该改用 DotNetZip。

这个答案相当于一个丑陋的解决方法。我不是特别喜欢它,但它确实有效(除非它不起作用),即使只是为了GZipStream.

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

由于每个 gzip 文件(实际上每个 gzip 成员)都以

     +---+---+---+---+---+---+---+---+
     |     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,并在流中向后寻找形成正确 CRC32 和 ISIZE 的 8 个字节之后。

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

<讽刺>我多么喜欢封装。将所有有用的东西封装起来,给我们留下一个解压缩的 Stream,它完全适用于全知的 API 设计人员预见的一个用例.

下面是到目前为止有效的快速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;
}

按照Mark Adler的建议,我尝试了DotNetZip,瞧,它的GZipStream.Position属性不仅不会抛出,它甚至返回实际读入的gzip字节数(加上8,出于某种原因,我仍然需要弄清楚)。

所以它确实读起来比严格必要的更多,但它让我计算出回溯多少。

以下内容对我有用:

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;

相关内容

  • 没有找到相关文章

最新更新