GZipStream и DeflateStream не будут распаковывать все байты
Мне нужен способ сжать изображения в .net, поэтому я изучил использование класса .net GZipStream (или DeflateStream). Однако я обнаружил, что декомпрессия не всегда была успешной, иногда изображения декомпрессировались, а в других случаях я получал ошибку GDI +, что что-то было повреждено.
После изучения проблемы я обнаружил, что декомпрессия не возвращает все сжатые байты. Поэтому, если я сжал 2257974 байт, я бы иногда возвращал только 2257870 байт (реальные числа).
Самое забавное, что иногда это сработает. Поэтому я создал этот небольшой тестовый метод, который сжимает только 10 байт, и теперь я ничего не возвращаю.
Я попробовал это с помощью обоих классов сжатия GZipStream и DeflateStream, и я дважды проверил код на возможные ошибки. Я даже попытался позиционировать поток до 0 и смыть все потоки, но не повезло.
Вот мой код:
public static void TestCompression()
{
byte[] test = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
byte[] result = Decompress(Compress(test));
// This will fail, result.Length is 0
Debug.Assert(result.Length == test.Length);
}
public static byte[] Compress(byte[] data)
{
var compressedStream = new MemoryStream();
var zipStream = new GZipStream(compressedStream, CompressionMode.Compress);
zipStream.Write(data, 0, data.Length);
return compressedStream.ToArray();
}
public static byte[] Decompress(byte[] data)
{
var compressedStream = new MemoryStream(data);
var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress);
var resultStream = new MemoryStream();
var buffer = new byte[4096];
int read;
while ((read = zipStream.Read(buffer, 0, buffer.Length)) > 0) {
resultStream.Write(buffer, 0, read);
}
return resultStream.ToArray();
}
Ответы
Ответ 1
Вам нужно Close()
ZipStream
после добавления всех данных, которые вы хотите сжать; он сохраняет буфер неписаных байтов внутри (даже если вы Flush()
), который должен быть записан.
В общем случае Stream
есть IDisposable
, поэтому вы также должны быть using
каждый... (да, я знаю, что MemoryStream
не потеряет никаких данных, но если вы не попадайте в эту привычку, она укусит вас другими Stream
s).
public static byte[] Compress(byte[] data)
{
using (var compressedStream = new MemoryStream())
using (var zipStream = new GZipStream(compressedStream, CompressionMode.Compress))
{
zipStream.Write(data, 0, data.Length);
zipStream.Close();
return compressedStream.ToArray();
}
}
public static byte[] Decompress(byte[] data)
{
using(var compressedStream = new MemoryStream(data))
using(var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
using (var resultStream = new MemoryStream())
{ ... }
}
[изменить: обновить комментарий]
Re using
вещи вроде MemoryStream
- это всегда весело, с большим количеством голосов по обе стороны забора: но безупречно...
(риторический - мы все знаем ответ...) Как реализовано MemoryStream
? это байт [] (принадлежит .NET)? это файл с отображением памяти (принадлежащий ОС)?
Причина, по которой вы не using
, заключается в том, что вы даете возможность узнать о внутренних деталях реализации, как вы кодируете общедоступный API, т.е. вы просто нарушили законы инкапсуляции. Общественный API говорит: я IDisposable
; вы "владеете" мной; поэтому, когда вы закончите, это ваша работа Dispose()
.
Ответ 2
Также - имейте в виду, что DeflateStream в System.IO.Compression не реализует наиболее эффективный алгоритм спускания. Если вам нравится, есть альтернатива BCL GZipStream и DeflateStream; он реализован в полностью управляемой библиотеке на основе кода zlib, который работает лучше, чем встроенный поток {Deflate, GZip} в этом отношении. [Но вам все равно нужно закрыть() поток, чтобы получить полный байтовый поток. ]
Эти классы потоков отправляются в сборке DotNetZlib, доступной в дистрибутиве DotNetZip по адресу http://DotNetZip.codeplex.com/.