Запретить 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) читает в 16384 байтах из streamвместо фактических 13293 сжатых байтов в случае, который я проверял.

Предполагая, что я не знаю ни размера сжатой части файла заранее, ни количества байтов после сжатых данных, есть ли способ использовать GzipStream/DeflateStream

  1. поэтому он читает только сжатые данные из stream
  2. или, по крайней мере, выяснить, каков был размер сжатой части данных, так что я могу stream.Position -= actuallyRead - compressedSize вручную?

3 ответа

Этот интерфейс, по-видимому, не обеспечивает средства для выполнения того, что вы хотите, что является одной из многих причин не использовать.NET GZipStream или DeflateStream.

Вы должны использовать DotNetZip вместо этого.

Следуя совету Марка Адлера, я попробовал 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;

Этот ответ представляет собой уродливый обходной путь. Мне это не особо нравится, но оно работает (за исключением случаев, когда это не так), даже если только для 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, не распаковывая, и искать в потоке назад сразу после 8 байтов, образующих правильный CRC32 и ISIZE.

Ужасно, но я вас предупредил.

<Сарказм>Как я люблю инкапсуляцию. Инкапсулируя все полезные вещи, мы получаем декомпрессионный поток, который работает точно в одном случае, который предвидел всезнающий дизайнер 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;
}
Другие вопросы по тегам