Delate压缩格式
综述
被压缩的数据是许多块组成的集合,每个块的大小是任意的,但是要小于65535字节。
每个块都有两个部分:
- 一对霍夫曼编码树,霍夫曼树本身利用霍夫曼编码进行压缩。
- 被压缩的数据。
其中霍夫曼树使用长度数组生成。长度数组本身也用霍夫曼编码进行存储。
被压缩的数据包括两种类型的元素:
- literal bytes,在前32k中没有相匹配的字符串,即无法从前32k中复制出来的字符串。
- pointers,是一个数据对,形式为<length,backward distance>。限制backward distance的大小小于32K字节,length小于258字节,当并不限制被压缩块的大小。
每种类型的元素(literals, distance, lengths)在数据中都是以霍夫曼编码的形式出现的,用一颗树对literals和length进行编码,用另一颗树对距离进行编码。对每一块进行编码的树 用一个固定的组成形式出现在该块的压缩数据之前。
编码细节
编码中的数据是不定长的位序列,而不是字节。所以没有必要关心究竟是大端存储还是小端存储。
下面是如何将位序列打包成比特流的规则:
- 数据元素按照字节序号的增加来进行排列的。即先填充这个字节的最低有效位。
- 除了霍夫曼编码的数据元素外,都是以数据的最低有效位开始存储。
- 霍夫曼编码从最好有效位开始存储。
也就是说,数据进行处理的时候,如果不是霍夫曼编码,那么先读到的数据就是这个数据的最低位,如果是霍夫曼编码,读到的就是这个数据的最高位。
数据就按照字节顺序,从字节最低位开始读即可。
经过LZ77压缩后的数据的表示
再经过LZ77算法压缩之后的数据通过两个alphabet进行表示。
- literal/length alphabet,包含了符号和长度
- distance alphabet,表示向前的的偏移量
这里符号指字典中的元素,编码指某个元素的二进制编码。
literal/length alphabet
该表含义如下:
- 0-255:literal bytes,相当于符号本身,解码时直接复制到输出流中就可以了。
- 256:块的结束标志。表示压缩数据块的结束
- 257-285:需要复制的长度。在每个长度之后可能会跟着一部分额外的bit,真实的数值为这几个bit的数值加上base。
具体的表格如下:
Code | Length base | Extra bits |
---|---|---|
257 | 3 | 0 |
258 | 4 | 0 |
259 | 5 | 0 |
260 | 6 | 0 |
261 | 7 | 0 |
262 | 8 | 0 |
263 | 9 | 0 |
264 | 10 | 0 |
265 | 11 | 1 |
266 | 13 | 1 |
267 | 15 | 1 |
268 | 17 | 1 |
269 | 19 | 2 |
270 | 23 | 2 |
271 | 27 | 2 |
272 | 31 | 2 |
273 | 35 | 3 |
274 | 43 | 3 |
275 | 51 | 3 |
276 | 59 | 3 |
277 | 67 | 4 |
278 | 83 | 4 |
279 | 99 | 4 |
280 | 115 | 4 |
281 | 131 | 5 |
282 | 163 | 5 |
283 | 195 | 5 |
284 | 227 | 5 |
285 | 258 | 0 |
distance alphabet
distance的解码规则与上一个表相同。
Code | Length base | Extra bits |
---|---|---|
0 | 1 | 0 |
1 | 2 | 0 |
2 | 3 | 0 |
3 | 4 | 0 |
4 | 5 | 1 |
5 | 7 | 1 |
6 | 9 | 2 |
7 | 13 | 2 |
8 | 17 | 3 |
9 | 25 | 3 |
10 | 33 | 4 |
11 | 49 | 4 |
12 | 65 | 5 |
13 | 97 | 5 |
14 | 129 | 6 |
15 | 193 | 6 |
16 | 257 | 7 |
17 | 385 | 7 |
18 | 513 | 8 |
19 | 769 | 8 |
20 | 1025 | 9 |
21 | 1537 | 9 |
22 | 2049 | 10 |
23 | 3073 | 10 |
24 | 4097 | 11 |
25 | 6145 | 11 |
26 | 8193 | 12 |
27 | 12289 | 12 |
28 | 16385 | 13 |
29 | 24577 | 13 |
Deflate格式中的霍夫曼编码
在Deflate格式中,每个字母表使用的霍夫曼编码都有两个额外的规则:
- 每个长度对应的编码的取值必须是连续的,并且与他们代表的符号出现顺序相同。
- 较短的编码在索引时要先于较长的码。
虽然LZ算法已经减少了数据量,但可以用霍夫曼编码进行进一步的压缩。
Deflate格式中最长的编码长度为15。最短的长度为1,0是保留字,用来表示这个符号根本就没有在压缩数据中出现。
长度数组(Code length alphabet)
Deflate格式并不存储霍夫曼编码本身,而是存储编码的长度数组,并通过特定的算法反解出霍夫曼编码。具体解法如下:
假设一共有13个符号,字面值为0-12,编码的长度分别为{2, 0, 3, 4, 4, 0, 2, 4, 4, 0, 4, 5, 5},可以看到1,5,9根本就没有出现过。
- 统计每个编码长度出现的频数,{0, 0, 2, 1, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},比如2出现了两次,对应位置就是2,如上文所说,0表示这个编码没有出现过,在频数数组里,0出现的次数时钟为0.
- 决定初始编码的值。具体算法会在代码中给出。
- 最后输出实际的编码表
#include <iostream>
#include <fstream>
#include <algorithm>
using namespace std;
const int sym_num = 13;
unsigned int code_length[sym_num] = {2, 0, 3, 4, 4, 0, 2, 4, 4, 0, 4, 5, 5},real_code[sym_num];
void GetCode()
{
unsigned int code_num[16] = {0}, code[16] = {0};
for( auto i : code_length ) code_num[i] ++;
code_num[0] = 0;
for( int i = 1; i < 16; ++ i )
code[i] = (code[i-1] + code_num[i-1]) << 1;
for( int i = 0; i < sym_num; ++ i )
if( code_length[i] ) real_code[i] = code[code_length[i]] ++;
}
int main()
{
GetCode();
}
最终编码结果如下:
0 -> 00
1 -> null
2 -> 001
3 -> 0101
4 -> 1101
5 -> null
6 -> 10
7 -> 0011
8 -> 1011
9 -> null
10 -> 0111
11 -> 01111
12 -> 11111
这一部分数据是高位在左,比如存储1011,那么就按照1,0,1,1的顺序存储。
假如有一段序列:10101010101011111000101000010110111101010
利用字典树进行匹配:1010 1010 11111 00 01 01 00 00 1011 01 1110 1010
然后反解出来:3, 3, 3, 12, 0, 6, 6, 0, 0, 4, 6, 10, 4
对长度数组的编码
由于只需要存储literal/length alphabet和distance alphabet,所以长度数组最长也就315。为了进一步缩短,Deflate中采用了下面这种办法:
- 0-15为literal编码的长度
- 16:重复之前的长度x次,读2个额外的比特,假设数值为y,那么x=y+3
- 17:重复之前的长度x次,读3个额外的比特,假设数值为y,那么x=y+3
- 18:重复之前的长度x次,读7个额外的比特,假设数值为y,那么x=y+11
块的格式
每一块被压缩的数据以3个头比特位开始。
- 第一个bit表示BFINAL,只有当前块是最后一块的时候才需要设置BFINAL。
- 接下来两个bit表示BTYPE
- 00表示没有压缩
- 01表示用固定的霍夫曼编码压缩
- 10表示用动态的霍夫曼编码压缩
- 11表示错误或者保留
这三个比特位不需要在每个字节的开头,因为每一块可以不占用整数字节的长度。
无压缩的块
0 1 | 2 3 | 4… |
---|---|---|
LEN | NLEN | LEN byte of literal data |
被压缩的块
如前文所述,字母表的大小位0-255,length的大小位0-258,编码时literal和length被合并成一个字母表了,大小为0-258。其中0-255表示literal bytes,数值256表示块的结束。257-285代表长度编码,可能额外的位跟在后边。
额外的位应该被声明为整数,并且最高位在最前面。
通过固定霍夫曼码压缩
有规定的表格,按照表格进行解码即可。
Lit Value | Bits | Codes |
---|---|---|
0 - 143 | 8 | 00110000 - 10111111 |
144 - 255 | 9 | 110010000 - 0010111 |
256 - 279 | 8 | 0000000 - 0010111 |
数值286 - 287不会再压缩的数据中出现,但是参与数据的生成。
距离的编码0 - 31用固定的5bit来表示,可能会像上文一样存在额外的比特。
通过动态霍夫曼码压缩
大小(bit) | 名称 | 描述 |
---|---|---|
5 | NLIT-257 | literal length alphabet中符号的个数 共拥有NLIT的符号。注意这块数据的数值比实际上的数少了257 |
5 | NDIST-1 | distance alphabet中符号的个数,实际值比真实值少1 |
4 | NCLEN-4 | 有NCLEN个符号在code length alphabet中,实际值比真实值少4 |
3*NCLEN | 在code length alphabet中用来编码前缀码的长度 | 用3bit来表示前缀码表的长度,用这些值可以剪出来code length alphabet所用的霍夫曼树。 表中的下标按如下顺序排列,{16,17,18,0,8,7,9,6,10,15,11,4,12,3,13,2,14,1,15} 如果NCLEN比19小,未涉及的部分填0 |
可变长 | 在literal/length alphabet和distance alphabet中用来编码前缀码的长度 | 有NLIT+NDIST个元素被编码。先存储literal/length alphabet中前缀码的长度,然后是distance前缀码的长度。 |
可变长 | 被压缩的数据 | 根据两颗霍夫曼树进行解码。 |