Запретить 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
- поэтому он читает только сжатые данные из
stream
- или, по крайней мере, выяснить, каков был размер сжатой части данных, так что я могу
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
,
- или, по крайней мере, выяснить, каков был размер сжатой части данных, так что я могу
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. 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;
}