Deflate数据格式分析(RFC-1951)

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。

具体的表格如下:

CodeLength baseExtra bits
25730
25840
25950
26060
26170
26280
26390
264100
265111
266131
267151
268171
269192
270232
271272
272312
273353
274433
275513
276593
277674
278834
279994
2801154
2811315
2821635
2831955
2842275
2852580
distance alphabet

distance的解码规则与上一个表相同。

CodeLength baseExtra bits
010
120
230
340
451
571
692
7132
8173
9253
10334
11494
12655
13975
141296
151936
162577
173857
185138
197698
2010259
2115379
22204910
23307310
24409711
25614511
26819312
271228912
281638513
292457713

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 12 34…
LENNLENLEN byte of literal data
被压缩的块

如前文所述,字母表的大小位0-255,length的大小位0-258,编码时literal和length被合并成一个字母表了,大小为0-258。其中0-255表示literal bytes,数值256表示块的结束。257-285代表长度编码,可能额外的位跟在后边。

额外的位应该被声明为整数,并且最高位在最前面。

通过固定霍夫曼码压缩

有规定的表格,按照表格进行解码即可。

Lit ValueBitsCodes
0 - 143800110000 - 10111111
144 - 2559110010000 - 0010111
256 - 27980000000 - 0010111

数值286 - 287不会再压缩的数据中出现,但是参与数据的生成。

距离的编码0 - 31用固定的5bit来表示,可能会像上文一样存在额外的比特。

通过动态霍夫曼码压缩
大小(bit)名称描述
5NLIT-257literal length alphabet中符号的个数
共拥有NLIT的符号。注意这块数据的数值比实际上的数少了257
5NDIST-1distance alphabet中符号的个数,实际值比真实值少1
4NCLEN-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前缀码的长度。
可变长被压缩的数据根据两颗霍夫曼树进行解码。
  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值