ZIP压缩的原理

ZIP压缩的原理

接着上一篇,我们学会了用vs2013将zlib.h头文件载入了vs2013,我们可以调用压缩和解压函数来实现这些操作,今天我们来一起学习下zip压缩的原理。

zip压缩的原理分为以下步骤:

1,将数据进行LZ压缩:我们以一个非常金典的例子来说明:生,容易。活,容易。生活,不容易。所谓LZ压缩就是当扫描到了某一个数据是,回过头来看前面扫描的数据中有没有出现这个数据,如果出现了就用这个数据与前面出现的数据的距离distance和重复的字符数长度lenght来表示,而没有重复的数据用literal表示,下图给出的例子就非常清晰:

更具上图,滑动窗口的大小决定着压缩的程度,越大压缩的越严重,但是滑动窗口的大小越大,压缩的速度也就越慢,在速度和压缩程度上我们合理分配权重,最后取32KB是最合适的。

2,下面就到了Zlib压缩的核心,主要是对 distance,lenght/literal进行再压缩,这里先讲对 distance的操作。

假如对一个文件进行LZ压缩后,得到的distance值为:

3、6、4、3、4、3、4、3、5

这个例子里,3出现了4次,4出现了3次,5出现了1次,6出现了1次。当然,不同的文件得到的结果不同,这里只是举一个典型的例子,因为只有4种值,所以我们没有必要对其它整数编码。只需要对这4个整数进行编码即可。而编码的规则是小的值就用较少比特表示,大的值就用较多比特表示。我们不妨用固定2个字节表示:

00-->3; 01-->4; 10-->5;  11-->6

00、01这种编码结果称为码字,码字的平均长度是2。上面这个对应关系即为码表,在压缩时,需要将码表放在最前面,后面的数字就用码字表示,解码时,先把码表记录在内存里,比如用一个哈希表记录下来,解压缩时,遇到00,就解码为3等等。

但是,前面说了编码的规则是小的值就用较少比特表示,大的值就用较多比特表示,所以这里用如下的方法表示为:

0-->3;10-->4;110-->5;111-->6

这样的编码思想和Huffman编码的方法非常相似,同时需要注意的一个原则是一个码字不能是另一个码字的前缀!

上面的结果可以用一颗二叉树表示为下图:


理解了Huffman编码的思想,我们来看看distance的实际情况。ZIP中滑动窗口大小固定为32KB,也就是说,distance的值范围是1-32768。那么,通过上面的方式,统计频率后,就得到32768个码字,按照上面这种方式可以构建出来。于是我们会遇到一个最大的问题,那就是这棵树太大了,怎么记录呢?

ZIP中Huffman码树的记录方式:

在这里zlib用一个非常巧妙的办法,它规定了Huffman码树的格式,都为如上图所示的。那么就只需要记录码字长度即可,上面的码表可以用下面的方式来记录:

3  4  5  6

1  2  3  3

我们知道每个distance肯定对应唯一一个码字,使用Huffman编码可以得到所有码字,但是因为distance可能非常多,虽然一般不会有32768这么多,但对一个大些的文件进行LZ编码,distance上千还是很正常的,所以这棵树很大。实际上,考虑压缩效率的原因,我们把 distance划分成30个区间如下图

那么也许有人会问,当distance为19怎么办?在回答这个问题之前我先介绍一下上面的表的结构:

其中,左边的Code表示区间的编号,是0-29,共30个区间,这只是个编号,没有特别的含义,但Huffman就是对0-29这30个Code进行编码的,得到区间的码字;

bits表示distance的码字需要在Code的码字基础上扩展几位,比如0就表示不扩展,最大的13表示要扩展13位,因此,最大的区间包含的distance数量为8192个。

Distance一列则表示这个区间涵盖的distance范围。

解决办法是这样的:distance为19在17-24这个区间,比如,17-24这个区间的Huffman码字是110,因为17-24这个区间有8个整数,于是按照下面的规则即可获得其distance对应的码字:

17-->110 000

18-->110 001

19-->110 010

20-->110 011

21-->110 100

22-->110 101

23-->110 110

24-->110 111

所以可以非常明确的知道19对应的数值是 110 010

3,ZIP中literal和length的压缩方式

literal和length也是类似于distance的方式进行压缩。

literal表示未匹配的字符,它实际上是针对字节作为基本字符来编码的,所以一个literal至多有256种可能。而length表示重复字符串长度,length=1当然不会出现,因为一个字符不值得用distance+length去记录,重复字符串当然越长越好,zlib中把length最小值认为是3,必须3个以上字符的字符串出现重复才用distance+length记录。同时把length的范围做了限制,限定length的个数跟literal一样,也只有256个。

literal用整数0-255表示,256是一个结束标志,解码以后结果是256表示解码结束;从257开始表示length,所以257这个数表示length=3,258这个数表示length=4等等,但zlib也不是一直这么一一对应,和distance一样,也是把length(总共256个值)划分为29个区间,其结果如下图:

Huffman码树用一个码字长度序列表示,称为CL(Code Length),记录两个码表的码字长度序列分别记为CL1、CL2。码树记录下来,对literal/length的编码比特流称为LIT比特流;对distance的编码比特流称为DIST比特流。按照上面的方法,LZ的编码结果就变成四块:CL1、CL2、LIT比特流、DIST比特流。CL1、CL2是码字长度的序列,这个序列说白了就是一堆正整数,因此,zlib继续深挖,认为这个序列还应该继续压缩,也就是说,对码表进行压缩

4,ZIP中对CL进行再次压缩的方法

这里仍然沿用Huffman的想法,因为CL也是一堆整数,那么当然可以再次应用Huffman编码。不过在这之前,zlib对CL序列进行了一点处理。这个处理也是很精巧的。

这个处理叫游程处理

什么叫游程呢?就是一段完全相同的数的序列。什么叫游程编码呢?说起来原理更简单,就是对一段连续相同的数,记录这个数一次,紧接着记录出现了多少个即可。为什么在这里引入游程处理了,是因为CL序列表示一系列整数对应的码字长度,对于literal/length来说,总共有0-285这么多符号,所以这个序列长度为286,每个符号都有一个码字长度,当然,这里面可能会出现大段连续的0,因为某些字符或长度不存在,尤其是对英文文本编码的时候,非ASCII字符就根本不会出现,length较大的值出现概率也很小,所以出现大段的0是很正常的;对于distance也类似,也可能出现大段的0。

下面通过一个例子来具体解说。David的书中举了这个例子,比如CL序列如下:

4, 4, 4, 4, 4, 3, 3, 3, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2
那么,游程编码的结果为:

4, 16, 01(二进制), 3, 3, 3, 6, 16, 11(二进制), 16, 00(二进制), 17,011(二进制), 2, 16, 00(二进制)
这是什么意思呢?因为CL的范围是0-15,PK认为重复出现2次太短就不用游程编码了,所以游程长度从3开始。用16这个特殊的数表示重复出现3、4、5、6个这样一个游程,分别后面跟着00、01、10、11表示(实际存储的时候需要低比特优先存储,需要把比特倒序来存,博文的一些例子有时候会忽略这点,实际写程序的时候一定要注意,否则会得到错误结果)。于是4,4,4,4,4,这段游程记录为4,16,01,也就是说,4这个数,后面还会连续出现了4次。6,16,11,16,00表示6后面还连续跟着6个6,再跟着3个6;因为连续的0出现的可能很多,所以用17、18这两个特殊的数专门表示0游程,17后面跟着3个比特分别记录长度为3-10(总共8种可能)的游程;18后面跟着7个比特表示11-138(总共128种可能)的游程。17,011(二进制)表示连续出现6个0;18,0111110(二进制)表示连续出现62个0。总之记住,0-15是CL可能出现的值,16表示除了0以外的其它游程;17、18表示0游程。因为二进制实际上也是个整数,所以上面的序列用整数表示为:

4, 16, 1, 3, 3, 3, 6, 16, 3, 16, 0, 17, 3, 2, 16, 0

我们又看到了一串整数,这串整数的值的范围是0-18。这个序列称为SQ(Sequence的意思)。因为有两个CL1、CL2,所以对应的有两个SQ1、SQ2。

针对SQ1、SQ2,PK用了第三个Huffman码表来对这两个序列进行编码。通过统计各个整数(0-18范围内)的出现次数,按照相同的思路,对SQ1和SQ2进行了Huffman编码,得到的码流记为SQ1 bits和SQ2 bits。同时,这里又需要记录第三个码表,称为Huffman码表3。同理,这个码表也用相同的方法记录,也等效为一个码长序列,称为CCL,因为至多有0-18个,zlib认为树的深度至多为7,于是CCL的范围是0-7。

就在大家都认为结束了的时候,zlib进行了置换CCL序列的操作哭。zlib认为CL序列里面CL范围为0-15,特殊的几个值是16、17、18,如果把CCL序列位置置换一下,把16、17、18这些放前面,那么这个CCL序列就很可能最后面跟着一串0(因为CL=14,15这些很可能没有),所以最后还引入了一个置换,其示意图如下,分别表示置换前的CCL序列和置换后的CCL。



到此为止,ZIP压缩算法的结果已经完毕。这个算法命名为Deflate算法。总结一下其编码流程为:这个图是精髓















评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值