专栏名称: 算法与数据结构
算法与数据结构知识、资源分享
目录
相关文章推荐
九章算法  ·  看清一个方向 ·  2 天前  
九章算法  ·  K.O大厂“原题”的《OOD面向对象圣经》, ... ·  2 天前  
算法爱好者  ·  IT巨头老板主张“每周 70 ... ·  4 天前  
九章算法  ·  今日开播!FAANG面试官划2025年上岸考 ... ·  5 天前  
51好读  ›  专栏  ›  算法与数据结构

ZIP压缩算法详细分析及解压实例解释(下)

算法与数据结构  · 公众号  · 算法  · 2016-10-10 11:51

正文

来源:esingchan - 博客园

链接:www.cnblogs.com/esingchan/p/3958962.html(点击尾部阅读原文前往)


上一篇文章同时推送的,也返回上一级,回复:238 获取


7、ZIP中对CL进行再次压缩的方法


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


CL序列表示一系列整数对应的码字长度,对于literal/length来说,总共有0-285这么多符号,所以这个序列长度为286,每个符号都有一个码字长度,当然,这里面可能会出现大段连续的0,因为某些字符或长度不存在,尤其是对英文文本编码的时候,非ASCII字符就根本不会出现,length较大的值出现概率也很小,所以出现大段的0是很正常的;对于distance也类似,也可能出现大段的0。PK于是先进行了一下游程编码。在说什么是游程编码之前,我们谈谈PK对CL序列的认识。


literal/length的编码符号总共286个(回忆:256个Literal+1个结束标志+29个length区间),distance的编码符号总共30个(回忆:30个区间),所以这颗码树不会特别深,Huffman编码后的码字长度不会特别长,PK认为最长不会超过15,也就是树的深度不会超过15,这个是否是理论证明我还没有分析,有兴趣的同学可以分析一下。因此,CL1和CL2这两个序列的任意整数值的范围是0-15。0表示某个整数没有出现(比如literal=0x12, length Code=8, distance Code=15等等)。


什么叫游程呢?就是一段完全相同的数的序列。什么叫游程编码呢?说起来原理更简单,就是对一段连续相同的数,记录这个数一次,紧接着记录出现了多少个即可。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个,PK认为树的深度至多为7,于是CCL的范围是0-7。


当得到了CCL序列后,PK决定不再折腾,对这个序列用普通的3比特定长编码记录下来即可,即000代表0,111代表7。但实际上还有一点小折腾,就是最后这个序列如果全部记录,那就需要19*3=57个比特,PK认为CL序列里面CL范围为0-15,特殊的几个值是16、17、18,如果把CCL序列位置置换一下,把16、17、18这些放前面,那么这个CCL序列就很可能最后面跟着一串0(因为CL=14,15这些很可能没有),所以最后还引入了一个置换,其示意图如下,分别表示置换前的CCL序列和置换后的CCL。可以看出,16、17、18对应的CCL被放到了前面,这样如果尾部出现了一些0,就只需要记录CCL长度即可,后面的0不记录。可以继续节省一些比特,不过这个例子尾部置换后只有1个0:



不过粗看起来,这个置换效果并不好,我一开始接触这个置换的时候,我觉得应该按照16、17、18、0、1、2、3、。。。这样的顺序来存储,如果按照我理解的,那么置换后的结果如下:


2、4、0、4、5、5、1、5、0、6、0、0、0、0、0、0、0、0、0


这样后面的一大串0直接截断,比PK的方法更短。但PK却按照上面的顺序。我总是认为,我觉得牛人可能出错了的时候,往往是我自己错了,所以我又仔细想了一下,上面的顺序特点比较明显,直观上看,PK认为CL为0和中间的值出现得比较多(放在了前面),但CL比较小的和比较大的出现得比较少(1、15、2、14这些放在了后面,你看,后面交叉着放),在文件比较小的时候,这种方法效果不算好,上面就是一个典型的例子,但文件比较大了以后,CL1、CL2码树比较大,码字长度普遍比较长,大部分很可能接近于中间值,那么这个时候PK的方法可能就体现出优势了。不得不说,对一个算法或者数据结构的优化程度,简直完全取决于程序员对那个东西细节的理解的深度。当我仔细研究了ZIP压缩算法的过程之后,我对PK这种深夜埋头冥思苦想的大牛佩服得五体投地。


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



8、Deflate压缩数据格式


ZIP的格式实际上就是Deflate压缩码流外面套了一层文件相关的信息,这里先介绍Deflate压缩码流格式。其格式为:



Header:3个比特,第一个比特如果是1,表示此部分为最后一个压缩数据块;否则表示这是.ZIP文件的某个中间压缩数据块,但后面还有其他数据块。这是ZIP中使用分块压缩的标志之一;第2、3比特表示3个选择:压缩数据中没有使用Huffman、使用静态Huffman、使用动态Huffman,这是对LZ77编码后的literal/length/distance进行进一步编码的标志。我们前面分析的都是动态Huffman,其实Deflate也支持静态Huffman编码,静态Huffman编码原理更为简单,无需记录码表(因为PK自己定义了一个固定的码表),但压缩率不高,所以大多数情况下都是动态Huffman。


HLIT:5比特,记录literal/length码树中码长序列(CL1)个数的一个变量。后面CL1个数等于HLIT+257(因为至少有0-255总共256个literal,还有一个256表示解码结束,但length的个数不定)。


HDIST:5比特,记录distance码树中码长序列(CL2)个数的一个变量。后面CL2个数等于HDIST+1。哪怕没有1个重复字符串,distance都为0也是一个CL。


HCLEN:4比特,记录Huffman码表3中码长序列(CCL)个数的一个变量。后面CCL个数等于HCLEN+4。PK认为CCL个数不会低于4个,即使对于整个文件只有1个字符的情况。


接下来是3比特编码的CCL,一共HCLEN+4个,用以构造Huffman码表3;


接下来是对CL1(码长)序列经过游程编码(SQ1:缩短的整数序列)后,并对SQ1继续用Huffman编码后的比特流。包含HLIT+257个CL1,其解码码表为Huffman码表3,用以构造Huffman码表1;


接下来是对CL2(码长)序列经过游程编码(SQ2:缩短的整数序列)后,并对SQ2继续用Huffman编码后的比特流。包含HDIST+1个CL2,其解码码表为Huffman码表3,用于构造Huffman码表2;


总之,上面的数据都是为了构造LZ解码需要的2个Huffman码表。


接下来才是经过Huffman编码的压缩数据,解码码表为Huffman码表1和码表2。
最后是数据块结束标志,即literal/length这个码表输入符号位256的编码比特。


对倒数第1、2内容块进行解码时,首先利用Huffman码表1进行解码,如果解码所得整数位于0-255之间,表示literal未匹配字符,接下来仍然利用Huffman码表1解码;如果位于257-285之间,表示length匹配长度,之后需要利用Huffman码表2进行解码得到distance偏移距离;如果等于256,表示数据块解码结束。


9、ZIP文件格式解析


上面各节对ZIP的原理进行了分析,这一节我们来看一个实际的例子,为了更好地描述,我们尽量把这个例子举得简单一些。下面是我随便从一本书拷贝出来的一段较短的待压缩的英文文本数据:


As mentioned above,there are many kinds of wireless systems other than cellular.


这段英文文本长度为80字节。经过ZIP压缩后,其内容如下:



可以看到,第1、2字节就是PK。看着怎么比原文还长,这怎么叫压缩?实际上,这里面大部分内容是ZIP的文件标记开销,真正压缩的内容(也就是我们前面提到的Deflate数据,划线部分都是ZIP文件开销)其实肯定要比原文短(否则ZIP不会启用压缩),我们这个例子是个短文本,但对于更长的文本而言,那ZIP文件总体长度肯定是要短于原始文本的。上面的这个ZIP文件,可以看到好几个以PK开头的区域,也就是不同颜色的划线区域,这些其实都是ZIP文件本身的开销。


所以,我们首先来看一看ZIP的格式,其格式定义为:


[local file header 1]
[file data 1]
[data descriptor 1]
……….
[local file header n]
[file data n]
[data descriptor n]
[archive decryption header]
[archive extra data record]
[central directory]
[zip64 end of central directory record]
[zip64 end of central directory locator]
[end of central directory record]

local file header+file data+data descriptor这是一段ZIP压缩数据,在一个ZIP文件里,至少有一段,至多那就不好说了,假如你要压缩的文件一共有10个,那这个地方至少会有10段,ZIP对每个文件进行了独立压缩,RAR在此进行了改进,将多个文件联合起来进行压缩,提高了压缩率。local file header的格式如下:



可见,起始的4个字节就是0x50(P)、0x4B(K)、0x03、0x04,因为是低字节优先,所以Signature=0x03044B50.接下来的内容按照上面的格式解析,十分简单,这个区域在上面ZIP数据的那个图里面是红色划线区域,之后则是压缩后的Deflate数据。在文件的尾部,还有ZIP尾部数据,上面这个例子包含了central directory和end of central directory record,一般这两部分也是必须的。central directory以0x50、0x4B、0x01、0x02开头;end of central directory record以0x50、0x4B、0x05、0x06开头,其含义比较简单,分别对应于上面ZIP数据那个图的蓝色和绿色部分,下面是两者的格式:



end of central directory record格式:



这几张图是我从网上找的,写得比较清晰。对于其中的含义,解释起来也比较简单,我分析的结果如下:注意ZIP采用的低字节优先,在一个字节里面低位优先,需要反过来看。


Local File Header: (38B,304b)
00001010110100101100000000100000 (signature)
0000000000010100 (version:20)
0000000000000000 (generalBitFlag)
0000000000001000 (compressionMethod:8)
0100110110001110 (lastModTime:19854)
0100010100100101 (lastModDate:17701)
01010100101011010100001100111100 (CRC32)
00000000000000000000000001001000 (compressedSize:72)
00000000000000000000000001010000 (uncompressedSize:80)
0000000000001000 (filenameLength:8)
0000000000000000 (extraFieldLength:0)
0010101010100110110011100010111001110100001011100001111000101110 (fileName:Test.txt)
(extraField)

Central File Header: (54B,432b)
00001010110100101000000001000000 (signature)
0000000000010100 (versionMadeBy:20)
0000000000010100 (versionNeeded:20)
0000000000000000 (generalBitFlag)
0000000000001000 (compressionMethod:8)
0100110110001110 (lastModTime:19854)
0100010100100101 (lastModDate:17701)
01010100101011010100001100111100 (CRC32)
00000000000000000000000001001000 (compressedSize:72)
00000000000000000000000001010000 (uncompressedSize:80)
0000000000001000 (filenameLength:8)
0000000000000000 (extraFieldLength:0)
0000000000000000 (fileCommenLength:0)
0000000000000000 (diskNumberStart)
0000000000000001 (internalFileAttr)
10000001100000000000000000100000 (externalFileAttr)
00000000000000000000000000000000 (relativeOffsetLocalHeader)
0010101010100110110011100010111001110100001011100001111000101110 (fileName:Test.txt)
(extraField)
(fileComment)

end of Central Directory Record: (22B,176b)
00001010110100101010000001100000 (signature)
0000000000000000 (numberOfThisDisk:0)
0000000000000000 (numberDiskCentralDirectory:0)
0000000000000001 (EntriesCentralDirectDisk:1)
0000000000000001 (EntriesCentralDirect:1)
00000000000000000000000000110110 (sizeCentralDirectory:54)
00000000000000000000000001101110 (offsetStartCentralDirectory:110)
0000000000000000 (fileCommentLength:0)
(fileComment)

Local File Header Length:304
Central File Header Length:432
End Central Directory Record Length:176


可见,开销总的长度为38+54+22=114字节,整个文件长度为186字节,因此Deflate压缩数据长度为72字节(576比特)。尽管这里看起来只是从80字节压缩到72字节,那是因为这是一段短文本,重复字符串出现较少,但如果文本较长,那压缩率就会增加,这里只是举个例子。


下面对其中的关键部分,也就是Deflate压缩数据进行解析。


10、Deflate解码过程实例分析


我们按照ZIP格式把Deflate压缩数据(72字节)提取出来,如下(每行8字节):


1010100001010011100010111011000000000001000001000011000010100010
1000101110101010011110110000000001100011101110000011100010100101
0101001111001100000010001101001010010010000101101010101100001101
1011110100011111100011101111111001110010011101110110011100010101
0010110100010100101100110001100100000100110111101101111000011101
0010001001100110111001000010011001101010101000110110000001110101
0100011010010011100010110111001000111101101001011100101010010111
0111000011111000011110000011010111001011011111111100100010001001
1010001100001110000010101010111101101010100101111101011111100000


Deflate格式除了上面的介绍,也可以参考RFC1951,解析如下:


Header:101, 第一个比特是1,表示此部分为最后一个压缩数据块;后面的两个比特01表示采用动态哈夫曼、静态哈夫曼、或者没有编码的标志,01表示采用动态Huffman;在RFC1951里面是这么说明的:


00 – no compression

01 – compressed with fixed Huffman codes

10 – compressed with dynamic Huffman codes

11 – reserved (error)


注意,这里需要按照低比特在先的方式去看,否则会误以为是静态Huffman。

接下来:


HLIT:01000,记录literal/length码树中码长序列个数的一个变量,表示HLIT=2(低位在前),说明后面存在HLIT + 257=259个CL1,CL1即0-258被编码后的长度,其中0-255表示Literal,256表示无效符号,257、258分别表示Length=3、4(length从3开始)。因此,这里实际上只出现了两种重复字符串的长度,即3和4。回顾这个图可以更清楚:



继续:


HDIST:01010,记录distance码树中码长序列个数的一个变量,表示HDIST=10,说明后面存在HDIST+1=11个CL2,CL2即Distance Code=0-10被编码的长度。


继续:


HCLEN:0111,记录Huffman码树3中码长序列个数的一个变量,表示HCLEN=14(1110二进制),即说明紧接着跟着HCLEN+4=18个CCL,前面已经分析过,CCL记录了一个Huffman码表,这个码表可以用一个码长序列表示,根据这个码长序列可以得到码表。于是接下来我们把后面的18*3=54个比特拷贝出来,上面的码流目前解析为下面的结果:


101(Header) 01000(HLIT) 01010(HDIST) 0111(HCLEN)
000 101 110 110 000 000 000 010 000 010 000 110 000 101 000 101 000 101 (CCL)
110101010011110110000000001100011101110000011100010100101
0101001111001100000010001101001010010010000101101010101100001101
1011110100011111100011101111111001110010011101110110011100010101
0010110100010100101100110001100100000100110111101101111000011101
0010001001100110111001000010011001101010101000110110000001110101
0100011010010011100010110111001000111101101001011100101010010111
0111000011111000011110000011010111001011011111111100100010001001
1010001100001110000010101010111101101010100101111101011111100000


标准的CCL长度为19(回忆一下:CCL范围为0-18,按照整数大小排序记录各自的码字长度),因此最后一个补0。得到序列:


000 101 110 110 000 000 000 010 000 010 000 110 000 101 000 101 000 101 000


其长度分别为(低位在前):


0、5、3、3、0、0、0、2、0、2、0、3、0、5、0、5、0、5、0

前面已经分析过,这个CCL序列实际上是经过一次置换操作得到的,需要进行相反的置换,置换后为:


3、5、5、5、3、2、2、0、0、0、0、0、0、0、0、0、0、5、3

这个就是对应于0-18的码字长度序列。


根据Deflate树的构造方式,得到下面的码表(Huffman码表3):


00         5
01         6
100       0
101       4
110       18
11100   1
11101   2
11110   3
11111   17


接下来就是CL1序列,按照前面的指示,一共有259个,分别对应于literal/length:0-258对应的码字长度序列,我们队跟着CCL后面的比特按照上面获得的码表进行逐步解码,在解码之前,实际上并不知道CL1的比特流长度有多少,需要根据259这个数字来判定,解完了259个整数,表明解析CL1完毕:


101(Header) 01000(HLIT) 01010(HDIST) 0111(HCLEN)
000 101 110 110 000 000 000 010 000 010 000 110 000 101 000 101 000 101 (CCL)


110(18)1010100(7比特,记录连续的11-138个0,此处一共0010101b=21,即记录21+11=32个0)


11110(3)110(18)0000000(7比特,记录连续的11-138个0,此处为全0,即记录0+11=11个0)


01(6)100(0)01(6)110(18)1110000(7比特,记录连续的11-138个0,此处为111b=7,即记录7+11=18个0)


01(6)110(18)0010100(7比特,记录连续的11-138个0,此处为10100b=20,即记录20+11=31个0)


101(4)01(6)01(6)00(5)11110(3)01(6)100(0)00(5)00(5)100(0)01(6)101(4)

00(5)101(4)00(5)100(0)100(0)00(5)101(4)101(4)01(6)01(6)01(6)100(0)


00(5)110(18)1101111(7比特,记录连续的11-138个0,此处为1111011b=123,即记录123+11=134个0)


统计一下,上面已经解了32+11+18+31+134+30=256个数了,因为总共259个,还差三个:


01(6)00(5)01(6)


好了,CL1比特流解析完毕了,得到的CL1码长序列为:


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 0 0 0 0 0 0
0 0 0 0 6 0 6 0 0 0 0 0 0 0 0 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 0 0 0 0 0 0 0 0 0 0 4 6 6 5 3 6 0 5 5 0 6 4 5 4 5 0 0 5 4 4 6 6 6
0 5 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 6 5 6


总共259个,每行40个。根据这个序列,同样按照Deflate树构造方法,得到literal/length码表(Huffman码表1)为:


000     –> (System.Char)(看前面的CL1序列,空格对应的ASCII为0x20=32,码字长度3,即上面序列中第一个3)


001     –>e(System.Char)
0100    –>a(System.Char)
0101    –>l(System.Char)
0110    –>n(System.Char)
0111    –>s(System.Char)
1000    –>t(System.Char)
10010   –>d(System.Char)
10011   –>h(System.Char)
10100   –>i(System.Char)
10101   –>m(System.Char)
10110   –>o(System.Char)
10111   –>r(System.Char)
11000   –>y(System.Char)
11001   –>3(System.Int32)(看前面的CL1序列,对应257,码字长度5)
110100  –>,(System.Char)
110101  –>.(System.Char)
110110  –>A(System.Char)
110111  –>b(System.Char)
111000  –>c(System.Char)
111001  –>f(System.Char)
111010  –>k(System.Char)
111011  –>u(System.Char)
111100  –>v(System.Char)
111101  –>w(System.Char)
111110  –>-1(System.Int32)(看前面的CL1序列,对应256,码字长度6)
111111  –>4(System.Int32)(看前面的CL1序列,对应258,码字长度6)


可以看出,码表里存在两个重复字符串长度3和4,当解码结果为-1(上面进行了处理,即256),或者说遇到111110的时候,表示Deflate码流结束。

按照同样的道理,对CL2序列进行解析,前面已经知道HDIST=10,即有11个CL2整数序列:


11111(17)000(3比特,记录连续的3-10个0,此处为0,即记录3个0)

11101(2)11111(17)100(3比特,记录连续的3-10个0,此处为001b=1,即记录4个0)


11100(1)100(0)11101(2)


已经结束,总共11个。


于是CL2序列为:


0 0 0 2 0 0 0 0 1 0 2


分别记录的是distance码为0-10的码字长度,根据下面的对应关系,需要进行扩展:



比如,第1个码长2记录的是Code=3的长度,即Distance=4对应的码字为:


10      –>4(System.Int32)


第1个码长1记录的是Code=8的长度(码字为0,扩展三位000-111),即Distance=17-24对应的码字为(注意,低比特优先):


0 000    –>17(System.Int32)
0 100    –>18(System.Int32)
0 010    –>19(System.Int32)
0 110    –>20(System.Int32)
0 001    –>21(System.Int32)
0 101    –>22(System.Int32)
0 011    –>23(System.Int32)
0 111    –>24(System.Int32)


注意,扩展的时候还是低比特优先。


最后1个码长2记录的是Code=10的长度(其实是码字:11,扩展四位0000-1111),即Distance=33-48对应的码字为:


11 0000  –>33(System.Int32)
11 1000  –>34(System.Int32)
11 0100  –>35(System.Int32)
11 1100  –>36(System.Int32)
11 0010  –>37(System.Int32)
11 1010  –>38(System.Int32)
11 0110  –>39(System.Int32)
11 1110  –>40(System.Int32)
11 0001  –>41(System.Int32)
11 1001  –>42(System.Int32)
11 0101  –>43(System.Int32)
11 1101  –>44(System.Int32)
11 0011  –>45(System.Int32)
11 1011  –>46(System.Int32)
11 0111  –>47(System.Int32)
11 1111  –>48(System.Int32)


至此为止,Huffman码表1、Huffman码表2已经还原出来,接下来是对LZ压缩所得到的literal、distance、length进行解码,目前剩余的比特流如下,先按照Huffman码表1解码,如果解码结果是长度(>256),则接下来按照Huffman码表2解码,逐步解码即可:


[As ]:110110(A)0111(s)000(空格)

[mentioned ]:10101(m)001(e)0110(n)1000(t)10100(i)10110(o)0110(n)001(e)10010(d)000(空格)

[above,]:0100(a)110111(b)10110(o)111100(v)001(e)110100(,)

[there ]:1000(t)10011(h)001(e)10111(r)001(e)000(空格)

[are ]:0100(a)11001(长度3,表示下一个需要用Huffman解码)10(Distance=4,即重复字符串为re空格)

[many ]:10101(m)0100(a)0110(n)11000(y)000(空格)

[kinds ]:111010(k)10100(i)0110(n)10010(d)0111(s)000(空格)

[of ]:10110(o)111001(f)000(空格)

[wireless ]:111101(w)10100(i)10111(r)001(e)0101(l)001(e)0111(s)0111(s)000(空格)

[systems o]:0111(s)11000(y)0111(s)1000(t)001(e)10101(m)11001(长度指示=3,接下来根据distance解码)0110(Distance=20,即重复字符串为s o)

[ther ]:111111(长度指示=4,接下来根据distance解码)111001(Distance=42,即重复字符串为ther)000(空格)

[than ]:1000(t)10011(h)0100(a)0110(n)000(空格)

[cellular.]:111000(c)001(e)0101(l)0101(l)111011(u)0101(l)0100(a)10111(r)110101(.)

[256,结束标志]111110(结束标志)0000(字节补齐的0)


于是解压缩结果为:


As mentioned above,there are many kinds of wireless systems other than cellular.


再来回顾我们的解码过程:


译码过程:


1、根据HCLEN得到截尾信息,并参照固定置换表,根据CCL比特流得到CCL整数序列;
2、根据CCL整数序列构造出等价于CCL的二级Huffman码表3;
3、根据二级Huffman码表3对CL1、CL2比特流进行解码,得到SQ1整数序列,SQ2整数序列;
4、根据SQ1整数序列,SQ2整数序列,利用游程编码规则得到等价的CL1整数序列、CL2整数序列;
5、根据CL1整数序列、CL2整数序列分别构造两个一级Huffman码表:literal/length码表、distance码表;
6、根据两个一级Huffman码表对后面的LZ压缩数据进行解码得到literal/length/distance流;
7、根据literal/length/distance流按照LZ规则进行解码。


Deflate码流长度总共为72字节=576比特,其中:


3比特Header;

5比特HLIT;

5比特HDIST;

4比特HCLEN;

54比特CCL序列码流;

133比特CL1序列码流;

34比特CL2序列码流;

338比特LZ压缩后的literal/length/distance码流。


11、ZIP的其它说明


上面各个环节已经详细分析了ZIP压缩的过程以及解码流程,通过对一个实例的解压缩过程分析,可以彻底地掌握ZIP压缩和解压缩的原理和过程。还有一些情况需要说明:


(1)上面的算法复杂度主要在于压缩一端,因为需要统计literal/length/distance,创建动态Huffman码表,相反解压只需要还原码表后,逐比特解析即可,这也是压缩软件的一个典型特点,解压速度远快于压缩速度。


(2)上面我们分析了动态Huffman,对于LZ压缩后的literal/length/distance,也可以采用静态Huffman编码,这主要取决于ZIP在压缩中看哪种方式更节省空间,静态Huffman编码不需要记录码表,因为这个码表是固定的,在RFC1951里面也有说明。对于literal/length码表来说,需要对0-285进行编码,其码表为:



对于Distance来说,需要对Code=0-29的数进行编码,则直接采用5比特表示。Distance和动态Huffman一样,在此基础上进行扩展。


(3)ZIP中使用的LZ77算法是一种改进的LZ77。主要区别有两点:


1)标准LZ77在找到重复字符串时输出三元组(length, distance, 下一个未匹配的字符)(有兴趣可以关注LZ77那篇论文);Deflate在找到重复字符串时仅输出双元组(length, distance)。


2)标准LZ77使用”贪婪“的方式解析,寻找的都是最长匹配字符串。Deflate中不完全如此。David Salomon的书里给了一个例子:



对于上面这个例子,标准LZ77在滑动窗口中查找最长匹配字符串,找到的是”the”与前面的there的前三个字符匹配,这种贪婪解析方式逻辑简单,但编码效率不一定最高。Deflate则不急于输出,跳过t继续往后查看,发现”th ne”这5个字符存在重复字符串,因此,Deflate算法会选择将t作为未匹配字符输出,而对后面的匹配字符串用(length, distance)编码输出。显然,这样就提高了压缩效率,因为标准的LZ77找到的重复字符串长度为3,而Deflate找到的是5。换句话说,Deflate算法并不是简单的寻找最长匹配后输出,而是会权衡几种可行的编码方式,用其中最高效的方式输出。


12、总结


本篇博文对ZIP中使用的压缩算法进行了详细分析,从一个简单地例子出发,一步步地分析了PK设计Deflate算法的思路。最后,通过一个实际例子,分析了其解压缩流程。总的来看,ZIP的核心在于如何对LZ压缩后的literal、length、distance进行Huffman编码,以及如何以最小空间记录Huffman码表。整个过程充满了对数据结构尤其是树的深入优化利用。按照上面的分析,如果要对ZIP进行进一步改进,可以考虑的地方也有不少,典型的有:


(1)扩大LZ编码的滑动窗口的大小;


(2)将Huffman编码改进为算术编码等压缩率更高的方法,毕竟,Huffman的码字长度必须为整数,这就从理论上限制了它的压缩率只能接近于理论极限,但难以达到。我记得在JPEG图像编码领域,以前的JPEG采用了DCT变换编码+Huffman的方式,现在JPEG2000将其改为小波变换+算数编码,所以数据压缩也可以尝试类似的思路;


(3)将多个文件进行合并压缩,ZIP中,不同的文件压缩过程没有关系,独立进行,如果将它们合并起来一起进行压缩,压缩率可以得到进一步提高。


描述分析有误的地方,敬请指正。针对数据压缩相关的话题,后续会对HBase列压缩等等进行分析,看看ZIP这种文件压缩和HBase这种数据库数据压缩的区别和联系。



本文编号239,以后想阅读这篇文章直接输入239即可。

●输入m可以获取到文章目录。

推荐15个技术类公众微信

涵盖:程序人生、算法与数据结构、黑客技术与网络安全、大数据技术、前端开发、Java、Python、Web开发、安卓开发、iOS开发、C/C++、.NET、Linux、数据库、运维等。