GZipStream и DeflateStream не распаковывают все байты - PullRequest
35 голосов
/ 07 ноября 2008

Мне нужен был способ сжатия изображений в .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();
    }

Ответы [ 2 ]

49 голосов
/ 07 ноября 2008

Вам необходимо Close() ZipStream после добавления всех данных, которые вы хотите сжать; он сохраняет внутренний буфер неписанных байтов (даже если вы Flush()), которые должны быть записаны.

В общем, Stream - это IDisposable, поэтому вы также должны быть using каждый ... (да, я знаю, что 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())
    { ... }
}

[редактировать: обновленный комментарий] Не using такие вещи, как MemoryStream - это всегда весело, с большим количеством голосов по обе стороны забора: но в конечном итоге ...

(риторический - мы все знаем ответ ...) Как реализуется MemoryStream? это байт [] (принадлежит .NET)? это файл с отображенной памятью (принадлежит ОС)?

Причина, по которой вы не using, заключается в том, что вы позволяете знанию внутренних деталей реализации изменить то, как вы кодируете против общедоступного API - то есть вы просто нарушили законы инкапсуляции. Публичный API говорит: Я IDisposable; ты "владеешь" мной; следовательно, ваша работа - Dispose() меня, когда вы закончите.

3 голосов
/ 04 января 2009

Кроме того, имейте в виду, что DeflateStream в System.IO.Compression не реализует наиболее эффективный алгоритм дефляции. Если хотите, есть альтернатива BCL GZipStream и DeflateStream; он реализован в полностью управляемой библиотеке на основе кода zlib, которая в этом отношении работает лучше, чем встроенный поток {Deflate, GZip}. [Но вам все еще нужно закрыть () поток, чтобы получить полный поток сообщений. ]

Эти классы потоков поставляются в сборке DotNetZlib, доступной в дистрибутиве DotNetZip по адресу http://DotNetZip.codeplex.com/.

...