GZipStream и DeflateStream не распаковывают все байты
-
06-07-2019 - |
Вопрос
Мне нужен был способ сжатия изображений в .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();
}
Решение
Вам нужно закрыть ()
ZipStream
после добавления всех данных, которые вы хотите сжать; он внутренне сохраняет буфер неписанных байтов (даже если вы Flush ()
), которые должны быть записаны.
В общем, Stream
- это IDisposable
, поэтому вам также следует использовать
каждый ... (да, я знаю, что MemoryStream
не собирается терять какие-либо данные, но если вы не попадаете в эту привычку, он укусит вас другими Stream
).
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())
{ ... }
}
[редактировать: обновленный комментарий]
Не используйте с помощью
такие вещи, как MemoryStream
- это всегда весело, с множеством голосов по обе стороны от забора: но в конечном итоге ...
(риторический - мы все знаем ответ ...) Как реализован MemoryStream
? это байт [] (принадлежит .NET)? это файл с отображением в памяти (принадлежит ОС)?
Причина, по которой вы не используете
, заключается в том, что вы позволяете знанию внутренних деталей реализации изменить то, как вы кодируете против общедоступного API - то есть вы просто нарушили законы инкапсуляции. Публичный API говорит: я IDisposable
; Вы "владеете" мне; следовательно, ваша работа заключается в том, чтобы избавиться ()
от меня, когда вы закончите.
Другие советы
Кроме того, имейте в виду, что DeflateStream в System.IO.Compression не реализует наиболее эффективный алгоритм дефляции. Если хотите, есть альтернатива BCL GZipStream и DeflateStream; он реализован в полностью управляемой библиотеке на основе кода zlib, которая в этом отношении работает лучше, чем встроенный поток {Deflate, GZip}. [Но вам все еще нужно закрыть () поток, чтобы получить полный поток сообщений. ]
Эти потоковые классы поставляются в сборке DotNetZlib, доступной в дистрибутиве DotNetZip по адресу http: //DotNetZip.codeplex. ком / . Р>