Zlib компрессионный массив байтов?

У меня есть этот несжатый массив байтов:

0E 7C BD 03 6E 65 67 6C 65 63 74 00 00 00 00 00 00 00 00 00 42 52 00 00 01 02 01
00 BB 14 8D 37 0A 00 00 01 00 00 00 00 05 E9 05 E9 00 00 00 00 00 00 00 00 00 00
00 00 00 00 01 00 00 00 00 00 81 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 05 00 00 01 00 00 00

И мне нужно сжать его, используя алгоритм дефляции (реализованный в zlib), из того, что я искал эквивалент в С#, будет использовать GZipStream, но я не могу полностью соответствовать сжатому результату.

Вот код сжатия:

public byte[] compress(byte[] input)
{
    using (MemoryStream ms = new MemoryStream())
    {
        using (GZipStream deflateStream = new GZipStream(ms, CompressionMode.Compress))
        {
            deflateStream.Write(input, 0, input.Length);
        }
        return ms.ToArray();
    }
}

Вот результат приведенного выше сжимающего кода:

1F 8B 08 00 00 00 00 00 04 00 ED BD 07 60 1C 49 96 25 26 2F 6D CA 7B 7F 4A F5 4A
D7 E0 74 A1 08 80 60 13 24 D8 90 40 10 EC C1 88 CD E6 92 EC 1D 69 47 23 29 AB 2A
81 CA 65 56 65 5D 66 16 40 CC ED 9D BC F7 DE 7B EF BD F7 DE 7B EF BD F7 BA 3B 9D
4E 27 F7 DF FF 3F 5C 66 64 01 6C F6 CE 4A DA C9 9E 21 80 AA C8 1F 3F 7E 7C 1F 3F
22 7E 93 9F F9 FB 7F ED 65 7E 51 E6 D3 F6 D7 30 CF 93 57 BF C6 AF F1 6B FE 5A BF
E6 AF F1 F7 FE 56 7F FC 03 F3 D9 AF FB 5F DB AF 83 E7 0F FE 35 23 1F FE BA F4 FE
AF F1 6B FC 1A FF 0F 26 EC 38 82 5C 00 00 00

Вот результат, который я ожидаю:

78 9C E3 AB D9 CB 9C 97 9A 9E 93 9A 5C C2 00 03 4E 41 0C 0C 8C 4C 8C 0C BB 45 7A
CD B9 80 4C 90 18 EB 4B D6 97 0C 28 00 2C CC D0 C8 C8 80 09 58 21 B2 00 65 6B 08
C8

Что я делаю неправильно, может ли кто-нибудь помочь мне там?

Ответ 1

Во-первых, некоторая информация: DEFLATE - это алгоритм сжатия, он определен в RFC 1951. DEFLATE используется в форматах ZLIB и GZIP, определенных в RFC 1950 и 1952, которые по существу представляют собой тонкие обертки вокруг потоков DEFLATE. Обертки предоставляют метаданные, такие как имя файла, временные метки, CRC или Adlers и т.д.

Библиотека базового класса .NET реализует метод DeflateStream, который создает исходный поток DEFLATE при использовании для сжатия. При использовании в декомпрессии он потребляет необработанный побочный поток DEFLATE..NET также предоставляет GZipStream, который является только оберткой GZIP вокруг этой базы. В библиотеке базового класса .NET нет ZlibStream - ничего, что производит или не потребляет ZLIB. Есть некоторые трюки, чтобы сделать это, вы можете искать вокруг.

В дефляционной логике в .NET проявляется поведенческая аномалия, где ранее сжатые данные могут фактически накачиваться, значительно, когда "сжаты". Это было источником Ошибка подключения с Microsoft, и обсуждался здесь на SO. Это может быть то, что вы видите, насколько неэффективное сжатие. Microsoft отклонила ошибку, поскольку, хотя она неэффективна для экономии места, сжатый поток не является недопустимым, другими словами его можно "распаковать" любым совместимым движком DEFLATE.

В любом случае, как и кто-то другой, сжатый поток, создаваемый разными компрессорами, может не совпадать. Это зависит от настроек по умолчанию и настроек приложения для компрессора. Несмотря на то, что сжатые потоки отличаются друг от друга, они все равно могут распаковываться до одного и того же исходного потока. С другой стороны, вещь, которую вы использовали для сжатия, была GZIP, в то время как кажется, что вы хотите, это ZLIB. Хотя они связаны друг с другом, они не совпадают; вы не можете использовать GZipStream для создания ZLIB-байта. Это основной источник различий, который вы видите.


Я думаю, вам нужен поток ZLIB.

Свободный управляемый Zlib в DotNetZip project реализует сжатие потоков для всех трех форматов (DEFLATE, ZLIB, GZIP). DeflateStream и GZipStream работают так же, как и встроенные классы .NET, и там есть класс ZlibStream, который делает то, что, по вашему мнению, делает. Ни один из этих классов не демонстрирует аномалию поведения, описанную выше.


В коде это выглядит так:

    byte[] original = new byte[] {
        0x0E, 0x7C, 0xBD, 0x03, 0x6E, 0x65, 0x67, 0x6C,
        0x65, 0x63, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x42, 0x52, 0x00, 0x00,
        0x01, 0x02, 0x01, 0x00, 0xBB, 0x14, 0x8D, 0x37,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x05, 0xE9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x81, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
        0x01, 0x00, 0x00, 0x00
    };

    var compressed = Ionic.Zlib.ZlibStream.CompressBuffer(original);

Вывод выглядит следующим образом:

0000    78 DA E3 AB D9 CB 9C 97 9A 9E 93 9A 5C C2 00 03     x...........\...
0010    4E 41 0C 0C 8C 4C 8C 0C BB 45 7A CD 61 62 AC 2F     NA...L...Ez.ab./
0020    19 B0 82 46 46 2C 82 AC 40 FD 40 0A 00 35 25 07     ...FF,[email protected]@..5%.
0030    CE                                                  .

Чтобы распаковать,

    var uncompressed = Ionic.Zlib.ZlibStream.UncompressBuffer(compressed);

Вы можете увидеть документацию по статическому методу CompressBuffer.


ИЗМЕНИТЬ

Возникает вопрос: почему DotNetZip производит 78 DA для первых двух байтов вместо 78 9C? Разница несущественна. 78 DA кодирует "максимальное сжатие", а 78 9C кодирует "сжатие по умолчанию". Как вы можете видеть в данных, для этого небольшого образца фактические сжатые байты точно совпадают с использованием BEST или DEFAULT. Кроме того, информация об уровне сжатия не используется во время декомпрессии. Это не влияет на ваше приложение.

Если вы не хотите сжатия "max", другими словами, если вы очень настроены на получение 78 9C в качестве первых двух байтов, хотя это не имеет значения, то вы не можете использовать удобство CompressBuffer функция, которая использует лучший уровень сжатия под крышками. Вместо этого вы можете сделать это:

  var compress = new Func<byte[], byte[]>( a => {
        using (var ms = new System.IO.MemoryStream())
        {
            using (var compressor =
                   new Ionic.Zlib.ZlibStream( ms, 
                                              CompressionMode.Compress,
                                              CompressionLevel.Default )) 
            {
                compressor.Write(a,0,a.Length);
            }

            return ms.ToArray();
        }
    });

  var original = new byte[] { .... };
  var compressed = compress(original);

Результат:

0000    78 9C E3 AB D9 CB 9C 97 9A 9E 93 9A 5C C2 00 03     x...........\...
0010    4E 41 0C 0C 8C 4C 8C 0C BB 45 7A CD 61 62 AC 2F     NA...L...Ez.ab./
0020    19 B0 82 46 46 2C 82 AC 40 FD 40 0A 00 35 25 07     ...FF,[email protected]@..5%.
0030    CE                                                  .

Ответ 3

Просто у вас был заголовок GZip. Вам нужен более простой заголовок Zlib. ZLib имеет опции для заголовка GZip, заголовка Zlib или без заголовка. Как правило, заголовок Zlib используется, если данные не связаны с файлом диска (в этом случае используется заголовок GZip.) По-видимому, нет никакой возможности в библиотеке .Net писать заголовок zlib (хотя это, безусловно, самый распространенный заголовок, используемый в форматах файлов). Попробуйте http://dotnetzip.codeplex.com/.

Вы можете быстро протестировать все различные параметры zlib, используя HexEdit (Operations- > Compression- > Settings). См. http://www.hexedit.com. Мне потребовалось 10 минут, чтобы проверить ваши данные, просто вставив сжатые байты в HexEdit и распакуя. Также попытался сжать ваши байты orignal с заголовками GZip и ZLib в качестве двойной проверки. Обратите внимание, что вам, возможно, придется возиться с настройками, чтобы получить точно те байты, которые вы ожидали.