- PVSM.RU - https://www.pvsm.ru -
В этой части мы, как и в первой [1], распакуем файл gzip вручную, но теперь ещё и декодируем коды Хаффмана.
Для начала запишем данные на диск:
$ echo "hector the frantic father on an anchor or a rare fat cat sat on the ranch" > test-huff.txt
$ xxd test-huff.txt
00000000: 6865 6374 6f72 2074 6865 2066 7261 6e74 hector the frant
00000010: 6963 2066 6174 6865 7220 6f6e 2061 6e20 ic father on an
00000020: 616e 6368 6f72 206f 7220 6120 7261 7265 anchor or a rare
00000030: 2066 6174 2063 6174 2073 6174 206f 6e20 fat cat sat on
00000040: 7468 6520 7261 6e63 680a the ranch.
На этот раз файл получился размером 74 байта и содержит 13 символов:
a, c, e, f, h, i, n, o, r, s, t; пробел (0x20) и перевод каретки (0x0a).
В этой строке есть много повторений. Надеюсь, gzip это учтёт. Поскольку я работаю под Windows, то для распаковки использовал 7zip-zstd [2].
$ 7z a -mx9 test-huff.txt.gz .test-huff.txt
$ xxd test-huff.txt.gz
00000000: 1f8b 0808 d76f 6565 0200 7465 7374 2d68 .....oee..test-h
00000010: 7566 662e 7478 7400 158b 410a 0031 0c02 uff.txt...A..1..
00000020: effb 0abf 2621 257b 69c1 e6ff d480 1e64 ....&!%{i......d
00000030: c6ca e823 7425 96b8 fb0f 2c7a 0967 8393 ...#t%....,z.g..
00000040: 2873 8710 9543 11ee 75ad cc51 237d 0fc7 (s...C..u..Q#}..
00000050: 9797 d64a 0000 00 ...J...
Чтобы вы лучше поняли, как будет выглядеть декодирование, покажу первую строку декодированного потока gzip:
0101 1001 0001 1101 00111 010 000 1101 0101 1001 000
h e c t o r ' ' t h e ' '
Ну а подробности читайте далее.
Первые байты вполне понятны:
1f8b
— «магический», жёстко закодированный заголовок gzip.08
— означает метод сжатия DEFLATE.08(00001000)
— бит 3 установлен, значит, будет присутствовать имя файла.d76f 6565
— временна́я метка 1701146583, UTC Tue Nov 28 04:43:03 202302
— использование самого медленного метода сжатия.00
— операционная система Windows (пригождается для LF/CLRF). Да, я пишу файлы в Windows, так как недавно перестал использовать WSL, потому что в моём ноутбуке всего 8 ГБ RAM, чего сегодня для Windows явно недостаточно. Планирую приобрести Mac в рамках предстоящей «Чёрной пятницы».Следующие несколько байтов содержат имя файла:
74 65 73 74 2d 68 75 66 66 2e 74 78 74 00
t e s t - h u f f . t x t NUL
На этот раз мы пойдём другим путём. Наш текущий файл намного больше предыдущего, который составлял 9 байт, к тому же мы будем декодировать коды Хаффмана, что может оказаться довольно запутанным процессом. В связи с этим я решил использовать программу infgen, написанную соавтором самого gzip, Марком Адлером. Infgen способна декодировать файл gzip и сообщать информацию о каждом его байте. Благодарю Rendello с Hackenews за то, что посоветовал её [3].
Примечание: infgen требует наличия в системе zlib, что в случае с Windows может оказаться проблематичным. Мне пришлось установить MSYS и использовать команду
gcc ./infgen.c -lz -o ./infgen
Вместо того, чтобы каждый раз инспектировать поток битов вручную с помощью xxd
, я буду использовать вывод infgen в качестве руководства, декодируя текст от конца к началу. Если вам интересно, как я инспектирую поток битов, то подробно этот процесс описывается в первой части.
$.infgen.exe -dd .test-huff.txt.gz
! infgen 3.2 output
!
gzip
!
last ! 1
dynamic ! 10
count 259 12 16 ! 1100 01011 00010
[... additional output trimmed. See appendix for the full output]
Эти три бита говорят о том, что это единственный блок в потоке, и что он был сжат с использованием динамических кодов Хаффмана.
Мы не будем разбирать этот поток битов подробно, я лишь вкратце уточню, что он представляет данные после обработки алгоритмом DEFLATE, без байтов заголовков, CRC и Length (длины). Напомню, что биты здесь упакованы в порядке от младшего (LSB, Least Significant Bit) к старшему (MSB, Most Significant Bit), а все целые числа интерпретируются с прямым порядком байтов (за исключением кодов Хаффмана, которые упаковываются в порядке от старшего бита к младшему). xxd
выводит биты от старшего к младшему, поэтому читать их поток нужно в обратном направлении. Подробнее об этом сказано также в первой части проекта.
$ xxd -s 24 -l 55 -b .test-huff.txt.gz
00000018: 00010101 10001011 01000001 00001010 00000000 00110001 ..A..1
0000001e: 00001100 00000010 11101111 11111011 00001010 10111111 ......
00000024: 00100110 00100001 00100101 01111011 01101001 11000001 &!%{i.
0000002a: 11100110 11111111 11010100 10000000 00011110 01100100 .....d
00000030: 11000110 11001010 11101000 00100011 01110100 00100101 ...#t%
00000036: 10010110 10111000 11111011 00001111 00101100 01111010 ....,z
0000003c: 00001001 01100111 10000011 10010011 00101000 01110011 .g..(s
00000042: 10000111 00010000 10010101 01000011 00010001 11101110 ...C..
00000048: 01110101 10101101 11001100 01010001 00100011 01111101 u..Q#}
0000004e: 00001111
.
Думаю, что вместо декодирования таблицы Хаффмана информативнее будет двигаться от конца к началу. Поэтому начнём с заключительной таблицы, и потом разберём, как она кодируется с использованием только длины кодов. Затем мы посмотрим, как кодируются эти длины, используя схему кодировки Run-length (RLE, протяжённость серии), которая сама кодируется с помощью кодов Хаффмана.
Я намеренно ограничил содержимое файла 13 символами. Теперь посмотрим, как они были закодированы.
Ещё раз обращу ваше внимание. Поскольку коды Хаффмана упаковываются в порядке от старшего бита к младшему, infgen даёт развёрнутый вывод.
Символ | Код Хаффмана | Вывод Infgen |
' ' | 000 | 000 |
a | 001 | 100 |
r | 010 | 010 |
c | 1000 | 0001 |
e | 1001 | 1001 |
h | 1010 | 0101 |
t | 1011 | 1101 |
f | 11010 | 01011 |
n | 11011 | 11011 |
o | 11100 | 00111 |
s | 11101 | 10111 |
‘n’ | 111110 | 011111 |
i | 111111 | 11111 |
Эти коды, выстраиваясь по частоте вхождения символа в исходном материале, почти формируют красивый префиксный код Хаффмана. Но здесь есть разрывы, потому что gzip использует особый приём для повышения степени сжатия — совмещает литералы и длины в один алфавит Хаффмана. Это позволяет дополнительно сэкономить пространство вместо того, чтобы кодировать эти элементы с помощью двух отдельных кодов Хаффмана.
Под длинами подразумеваются длины последовательностей (match), то есть прохождение
N
шагов в обратном направлении. Ниже мы разберём этот процесс подробнее.
Тем не менее по неясной для меня причине расстояние кодируется с помощью отдельного алфавита. К чему это недоумение? ¯(ツ)/¯ …
Полное дерево Хаффмана len/lit
получается таким:
Символ | Код Хаффмана | Вывод Infgen |
' ' | 000 | 000 |
a | 001 | 100 |
r | 010 | 010 |
3 (len) | 011 | 110 |
c | 1000 | 0001 |
e | 1001 | 1001 |
h | 1010 | 0101 |
t | 1011 | 1101 |
4 (len) | 1100 | 0011 |
f | 11010 | 01011 |
n | 11011 | 11011 |
o | 11100 | 00111 |
s | 11101 | 10111 |
END | 11110 | 01111 |
‘n’ | 111110 | 011111 |
i | 111111 | 11111 |
Заметьте, поскольку мы написали коды Хаффмана в порядке возрастания, нам не обязательно конкретно указывать биты. Если мы просто будем знать длину кода каждого символа, то сможем в точности воссоздать этот код.
Рассмотрим символы с длиной 3: пробел
, a
, r
, 3
. Они выстроены в алфавитном порядке (по ASCII), охватывая коды от 000
до 011
. Символы с длиной 4, а именно c
, e
, h
, t
, также перечислены в алфавитном порядке и представлены кодами от 1000
до 1011
.
Эта техника называется каноническим кодированием Хаффмана [4] и представляет очень крутой приём для выражения кодов. По сути, любое кодирование по методу Хаффмана, даже в других форматах сжатия, работает таким образом. Если вам этот момент не до конца ясен, не поленитесь почитать статью в Википедии по прикреплённой ссылке.
Получается, мы можем представить наши коды Хаффмана так:
Символ | Код Хаффмана | Длина кода |
' ' | 000 | 3 |
a | 001 | 3 |
r | 010 | 3 |
3 (len) | 011 | 3 |
c | 1000 | 4 |
e | 1001 | 4 |
h | 1010 | 4 |
t | 1011 | 4 |
4 (len) | 1100 | 4 |
f | 11010 | 5 |
n | 11011 | 5 |
o | 11100 | 5 |
s | 11101 | 5 |
END | 11110 | 5 |
‘n’ | 111110 | 6 |
i | 111111 | 6 |
И при их декодировании нам нужно лишь передать длины кодов — так мы поймём, как воссоздать изначальный код Хаффмана. Алфавит len/lit
простирается от 0
до 285
, значит, можно представить наш код в виде массива длиной 286 элементов:
0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,3,0,4,0,4,5,0,4,6,0,0,0,0,5,5,0,0,3,5,4,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
5,3,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Но это крайне накладный вариант. Только посмотрите на все эти нули в середине, которые так и просят, чтобы их закодировали с помощью RLE. Это мы и сделаем. Нам нужна такая кодировка RLE, которая будет просто отражать количество нулей, сопровождаемое длиной кода. При этом 1-2 нуля будут кодироваться стандартно. Что-то вроде этого:
zeros 10, codelengths 6,
zeros 21, codelengths 3,
zeros 64, codelengths 3 0 4 0 4 5 0 4 6,
zeros 4, codelengths 5 5 0 0 3 5 4,
zeros 138, codelengths 0 5 3 4
rest zeroes
Уверен, вас не удивит, что gzip для уменьшения длины кодов использует именно эту кодировку. В ней:
0
до 15
представляет длину. Имейте в виду, это означает, что максимальная длина кода len/lit
будет 15.17
и 18
используются для представления коротких и длинных последовательностей нулей соответственно, сопровождаясь фиксированным числом битов, указывающих количество этих нулей.16
используется для обозначения повторений предыдущего элемента. В нашем случае отсутствует.Разберём пример. Для этого мы представим код выше так:
17 [10] 6
18 [21] 3
18 [64] 3 0 4 0 4 5 0 4 6
17 [4] 5 5 0 0 3 5 4
18 [138] 0 5 3 4
Числа в скобках отражают фиксированное количество нулей и к длинам кодов не относятся.
В завершение нужно указать, как представлять коды от 0
до 18
. Простейшей реализацией будет выразить их как фиксированные целые числа размером 5 бит. Но имейте в виду, что в нашем примере выше некоторые коды встречаются чаще других. Не будет ли грамотным решением сжать эти более частотные коды в меньшее число битов? Подождите-ка! Именно это и представляют из себя коды Хаффмана, и мы уже прописали весь механизм их обработки.
Итак, gzip представляет коды от 0
до 18
, используя второй порядок кодов Хаффмана. Вот таблица длин кодов для нашего примера:
Код | Код Хаффмана | Длина кода |
0 | 00 | 2 |
3 | 01 | 2 |
4 | 100 | 3 |
5 | 101 | 3 |
2 | 1100 | 4 |
6 | 1101 | 4 |
17 | 1110 | 4 |
18 | 1111 | 4 |
Эти длины кодов второго порядка (длины кодов длин кодов) далее кодируются в виде статических целых чисел размером 3 бита и выстраиваются в фиксированный 18-элементный массив.
2,0,4,2,3,3,4,0
0,0,0,0,0,0,0,0
0,4,4
Или должны были выстраиваться, если бы автор gzip не реализовал ещё один приём. Как мы видим, более значительные длины кодов, вроде 10-15, чаще представлены нулями. То же касается и наименьших длин вроде 1 и 2. Поэтому gzip «угадывает» ожидаемую частоту длин кодов и выстраивает их в порядке 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15. После этого он кодирует длину ненулевой части массива и отбрасывает все оставшиеся нули.
Такой порядок, который кажется совершенно загадочным, когда читаешь о нём в спецификации, становится полностью логичным, если учитывать вероятность встретить определённую длину кода.
Получается, наши длины должны кодироваться в следующем порядке:
0,4,4,2,0,0,0,4
0,3,0,3,0,2,0,4
0,0,0
«Ненулевая» длина массива равна 16 (последние три элемента нули), и она кодируется отдельно в заголовке блока. Значит, мы можем просто отбросить три хвостовых элемента и получить битовую кодировку:
000 100 100 010 000 000 000 100
000 011 000 011 000 010 000 100
Поскольку это не коды Хаффмана, а скорее целые числа с фиксированной длиной 3 бита, они упаковываются в порядке от младшего бита к старшему, как и любая другая сущность данных.
Этот момент не совсем ясен, но вроде работает всё именно так. Перед этим идут 17 бит, указывающие длины. Значит, наши 48 бит будут сжаты именно в таком виде, где x
представляет неизвестные биты других частей кода gzip:
0100000x 00001010 00000000 00110001 00001100 00000010 xxxxxxx1
А вот и оно!
00000018: 00010101 10001011 01000001 00001010 00000000 00110001 ..A..1
^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
0000001e: 00001100 00000010 11101111 11111011 00001010 10111111 ......
^^^^^^^^ ^^^^^^^^ ^
00000024: 00100110 00100001 00100101 01111011 01101001 11000001 &!%{i.
0000002a: 11100110 11111111 11010100 10000000 00011110 01100100 .....d
00000030: 11000110 11001010 11101000 00100011 01110100 00100101 ...#t%
00000036: 10010110 10111000 11111011 00001111 00101100 01111010 ....,z
0000003c: 00001001 01100111 10000011 10010011 00101000 01110011 .g..(s
00000042: 10000111 00010000 10010101 01000011 00010001 11101110 ...C..
00000048: 01110101 10101101 11001100 01010001 00100011 01111101 u..Q#}
0000004e: 00001111 .
Можно выдохнуть!
Описанный выше процесс полностью обратим, и я предлагаю реализовать его читателям самостоятельно в качестве упражнения.
А мы перейдём к фактической распаковке кодов Хаффмана, так как это будет более информативно. Напомню, вот поток битов, где выделена первая строка раздела сжатых данных, продолжающаяся до самого конца.
00000018: 00010101 10001011 01000001 00001010 00000000 00110001 ..A..1
0000001e: 00001100 00000010 11101111 11111011 00001010 10111111 ......
00000024: 00100110 00100001 00100101 01111011 01101001 11000001 &!%{i.
0000002a: 11100110 11111111 11010100 10000000 00011110 01100100 .....d
00000030: 11000110 11001010 11101000 00100011 01110100 00100101 ...#t%
^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
00000036: 10010110 10111000 11111011 00001111 00101100 01111010 ....,z
0000003c: 00001001 01100111 10000011 10010011 00101000 01110011 .g..(s
00000042: 10000111 00010000 10010101 01000011 00010001 11101110 ...C..
00000048: 01110101 10101101 11001100 01010001 00100011 01111101 u..Q#}
0000004e: 00001111 .
Вот первый раздел в декодированном виде:
0101 1001 0001 1101 00111 010 000 1101 0101 1001 000
h e c t o r ' ' t h e ' '
01011 010 100 11011 1101 111111 0001 000 01011 100
f r a n t i c ' ' f a
01 011 110
MATCH 3 14 (the)
Мы используем вывод infgen, где коды Хаффмана представлены в обратном порядке, чтобы облегчить их обнаружение в потоке битов. Если вы хотите сопоставить этот вывод с кодами, которые мы видели ранее, то просто разверните порядок его битов.
Сконцентрируемся на первой инструкции MATCH
. Значение 110
представляет код Length/Lit
для 257
. Поскольку литералы занимают диапазон только от 0
до 255
, это код длины, и он представляет последовательность длиной 3. Как далеко в обратном направлении нам нужно смотреть? Рядом с ним указан код расстояния, который также представлен в кодировке Хаффмана. Мы его не декодировали, но вот таблица этих кодов:
Код | Расстояние | Дополнительные биты | Код Хаффмана | Вывод Infgen |
9 | 25-32 | 3 | 00 | 00 |
10 | 33-48 | 4 | 01 | 10 |
2 | 3 | 0 | 100 | 001 |
3 | 4 | 0 | 101 | 101 |
7 | 13-16 | 2 | 110 | 011 |
11 | 49-64 | 4 | 111 | 111 |
Не все коды расстояния представляют его в точности. Некоторые отражают диапазон расстояний и сопровождаются дополнительными битами, уточняющими его фактическую величину.
В примере выше с MATCH 3 14
мы видим, что код расстояния (infgen) равен 011
. Это значение соответствует коду 7, из чего следует, что расстояние находится в диапазоне между 13 и 16 включительно. Следующие 2 бита дадут нам уже точное понимание. Поскольку это 01
, значит нам нужно отсчитать назад 14 позиций.
Для нахождения последовательности мы отсчитываем 14 букв назад и видим 1101
, или t
. Значит, последовательность длиной 3 символа означает the
. Всё верно — это часть слова father
.
Продолжим:
010 000 00111 11011 000 100
r ' ' o n ' ' a
001 0011
MATCH 4 3 (n an)
Здесь мы встречаем первую интересную последовательность. Заметьте, что она находится на расстоянии 3 букв от конца, но имеет длину 4. Таким образом gzip представляет повторяющийся элемент (в данном случае n
) в самой последовательности.
Чтобы его декодировать, нам нужно начать повторять часть самого декодируемого потока. Опять же, такой способ на первый взгляд кажется непонятным, но по факту оказывается очень удобным и лаконичным для представления очень длинных строк с повторениями.
Например, строку bananananana
можно представить как b, a, n, match 9 2
.
Завершим рассмотрение оставшегося кода, объединив всё:
0101 1001 0001 1101 00111 010 000 1101 0101 1001 000
h e c t o r ' ' t h e ' '
01011 010 100 11011 1101 111111 0001 000 01011 100
f r a n t i c ' ' f a
01 011 110
MATCH 3 14 (the)
010 000 00111 11011 000 100
r ' ' o n ' ' a
001 0011
MATCH 4 3 (n an)
0001 0101 111 00 110 001 110
c h MATCH 3 32 (or ) MATCH 3 3 (or )
100 000 010 100 010 1001
a ' ' r a r e
101 00 0011 000 0001 101 110
MATCH 4 30 ( fat) ' ' c MATCH 3 4 (at )
10111 101 110 0010 10 110
s MATCH 3 4 (at ) MATCH 3 35 (on )
1000 111 0011 010 0100 10 0011
MATCH 4 57 (the ) r MATCH 4 37 (anch)
011111 01111
LF END
Крутой ликбез, не так ли? В процессе я сделал несколько интересных наблюдений:
Было весело! В этой статье мы рассмотрели, как коды Хаффмана упаковываются и свёртываются в небольшое количество бит с помощью двухэтапной кодировки. Теперь вы полностью готовы к тому, чтобы самостоятельно выполнить декодирование, поскольку это тот же процесс, только в обратном порядке. Если у вас возникнут сложности, все ответы можно найти в выводе infgen.
После этого мы использовали «декодированные» коды Хаффмана, чтобы полностью декодировать сжатый вывод, и попутно разобрали принцип работы MATCH
.
Если найдёте какие-то ошибки, можете сообщить о них на GitHub [5] или отписать мне на почту thomastayac@gmail.com [6]
! infgen 3.2 output
!
time 1701146583 ! [UTC Tue Nov 28 04:43:03 2023]
xfl 2
os 0
name 'test-huff.txt
gzip
!
last ! 1
dynamic ! 10
count 259 12 16 ! 1100 01011 00010
code 17 4 ! 100 000
code 18 4 ! 100
code 0 2 ! 010
code 6 4 ! 100 000 000 000
code 5 3 ! 011 000
code 4 3 ! 011 000
code 3 2 ! 010 000
code 2 4 ! 100 000
zeros 10 ! 111 0111
lens 6 ! 1011
zeros 21 ! 0001010 1111
lens 3 ! 10
zeros 64 ! 0110101 1111
lens 3 ! 10
lens 0 ! 00
lens 4 ! 001
lens 0 ! 00
lens 4 ! 001
lens 5 ! 101
lens 0 ! 00
lens 4 ! 001
lens 6 ! 1011
zeros 4 ! 001 0111
lens 5 ! 101
lens 5 ! 101
lens 0 ! 00
lens 0 ! 00
lens 3 ! 10
lens 5 ! 101
lens 4 ! 001
zeros 138 ! 1111111 1111
lens 0 ! 00
lens 5 ! 101
lens 3 ! 10
lens 4 ! 001
lens 0 ! 00
lens 0 ! 00
lens 3 ! 10
lens 3 ! 10
zeros 3 ! 000 0111
lens 3 ! 10
lens 0 ! 00
lens 2 ! 0011
lens 2 ! 0011
lens 3 ! 10
! stats table 24:4
! litlen 10 6
! litlen 32 3
! litlen 97 3
! litlen 99 4
! litlen 101 4
! litlen 102 5
! litlen 104 4
! litlen 105 6
! litlen 110 5
! litlen 111 5
! litlen 114 3
! litlen 115 5
! litlen 116 4
! litlen 256 5
! litlen 257 3
! litlen 258 4
! dist 2 3
! dist 3 3
! dist 7 3
! dist 9 2
! dist 10 2
! dist 11 3
literal 'h ! 0101
literal 'e ! 1001
literal 'c ! 0001
literal 't ! 1101
literal 'o ! 00111
literal 'r ! 010
literal ' ! 000
literal 't ! 1101
literal 'h ! 0101
literal 'e ! 1001
literal ' ! 000
literal 'f ! 01011
literal 'r ! 010
literal 'a ! 100
literal 'n ! 11011
literal 't ! 1101
literal 'i ! 111111
literal 'c ! 0001
literal ' ! 000
literal 'f ! 01011
literal 'a ! 100
match 3 14 ! 01 011 110
literal 'r ! 010
literal ' ! 000
literal 'o ! 00111
literal 'n ! 11011
literal ' ! 000
literal 'a ! 100
match 4 3 ! 001 0011
literal 'c ! 0001
literal 'h ! 0101
match 3 32 ! 111 00 110
match 3 3 ! 001 110
literal 'a ! 100
literal ' ! 000
literal 'r ! 010
literal 'a ! 100
literal 'r ! 010
literal 'e ! 1001
match 4 30 ! 101 00 0011
literal ' ! 000
literal 'c ! 0001
match 3 4 ! 101 110
literal 's ! 10111
match 3 4 ! 101 110
match 3 35 ! 0010 10 110
match 4 57 ! 1000 111 0011
literal 'r ! 010
match 4 37 ! 0100 10 0011
literal 10 ! 011111
end ! 01111
! stats literals 3.8 bits each (153/40)
! stats matches 45.9% (10 x 3.4)
! stats inout 54:5 (50) 74 0
! 000
! stats total inout 54:5 (50) 74
! stats total block average 74.0 uncompressed
! stats total block average 50.0 symbols
! stats total literals 3.8 bits each
! stats total matches 45.9% (10 x 3.4)
!
crc
length
Автор: Дмитрий Брайт
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/389174
Ссылки в тексте:
[1] первой: https://habr.com/ru/companies/ruvds/articles/781862/
[2] 7zip-zstd: https://github.com/mcmilk/7-Zip-zstd
[3] посоветовал её: https://news.ycombinator.com/item?id=29337292
[4] каноническим кодированием Хаффмана: https://en.wikipedia.org/wiki/Canonical_Huffman_code
[5] на GitHub: https://github.com/thomastay/personal-blog/issues
[6] thomastayac@gmail.com: http://thomastayac@gmail.com
[7] Источник: https://habr.com/ru/companies/ruvds/articles/783210/?utm_source=habrahabr&utm_medium=rss&utm_campaign=783210
Нажмите здесь для печати.