ZIP压缩算法:看大神是如何设计的

来源:互联网 发布:网络歌手虞姬资料 编辑:程序博客网 时间:2024/05/19 11:44

      如题,开始看算法之前,先看一下该算法的作者,大牛Phil Katz(简称PK)的人生轨迹:
      PKzip创始人Phil Katz短暂而饱受折磨的一生
      好了,让我们怀着对命运深深的惋惜和对大神满满的敬意开始ZIP算法之旅:
      ZIP压缩算法详细分析及解压实例解释
      其实这篇文章讲的已经是比较清楚了,但因为讲到许多细节,内容还是偏长,我尽量简略总结和补充下。
      ZIP压缩的核心逻辑就是对重复的内容用其前面(32K Bytes)内容所在位置、长度来存储,不必存储内容本身。文件以字节为单位的字符串来处理,重复内容超过3Bytes时这样存储,最大为256 Bytes(超过就作为两部分重复内容),不重复就直接存为原来的内容。
       我们知道在信息论中,不定长编码(比如算法里用到的Huffman编码)存储时是根据字符出现的概率来进行编码设计的,出现次数越多码长就越短,这样就能使编码后的数据越短。
      这下好了,要压缩一个文件,首先要对该文件内容进行统计,看共有多少种重复内容的位置(理论上会有32K,但是一般不会有这么多,所以被PK给分成30个区间了)以及多少重复内容的长度(最多256种,当然被PK给分成29个区间了),以及多少种字符(最多256个,一般不会全出现,比如一篇英文文章)。
      然后呢,PK将字符种类数和重复内容的长度种类区间数合并考虑,256+29=285种,简单说来就是说不超过256就是没有重复的字符,不用解码,超过256小于285就是重复内容的长度种类区间代码,表示接下来你需要从前面内容来恢复数据了。我们之前说过,这285个种类数不可能全部出现,根据实际出现的概率来进行编码的话怎么编呢?Huffman编码,不多说了,码元满足无损解码和最短码元长度。这样我们根据字符情况,没有重复的字符就是直接用编码存储,是重复内容就先将长度编码存储(区间码元+区间内固定长度位移顺序码),然后再将位置编码存储(同样也是区间码元+区间内固定长度位移顺序码)。
这就是Zip压缩的基本逻辑,首先要根据内容统计设计好码元,然后按照码元进行内容编码。
接下来的问题,如何设计这两种码元(最大285以及30这两个),以及如何将码元信息存储。
       如何设计码元呢,PK按照一种特殊的Huffman树(右倾树)进行码元设计,以285种这个设计为例,即使最多的285种码元设计后最长的码元长度也不会超过15(文件大的话估计码元长度一般集中在6、7、8、9这几个长度,15、1、14、2之类的可能不会有或很少)。至于30个位置种类区间更不在话下,码元长度不超过7个,列出30个数即可,当然PK依旧省去后面的0,就可能不到30个了。
      如何存储码元呢,不是已经有了有285和30个设计好的码元吗,那么我们只要有了这一串码元长度信息序列就可以恢复出码元,为什么?就因为那棵特殊的Huffman编码树,PK真的对Huffman树理解的很深!
      这时你可以说,OK了,没必要多说了,285个码元长度数先按固定长度顺序列出(每个数不超过15嘛,285*4bits足矣,143Bytes),便于描述就设该序列为SQ1,再列出30个码元长度数的序列(每个数不超过7,30*3bits足矣,12Bytes),便于描述就设为SQ2。是的,我也这么想的,然而PK没有停止,这点数据也要压缩存储。好吧,直接的说就是对这两种码元长度序列再进行Huffman编码,序列中不是只有16个数嘛,给你补上3个便于游程编码,一共19个,这19个也不一定全出现,那么根据统计进行Huffman码元设计。同样,这个码元也用码元长度(不超过7)序列来存储,假设为CCL。好,现在设计好了存储SQ1和SQ2序列要用的码元,按照这个码元对之前的SQ1和SQ2就可以游程编码存储,来节省直接存储SQ1和SQ2内容所占空间。那么什么是游程编码呢?就是对一段连续出现字符(比如44444)采用4,16(有重复情况),11(重复次数,一般超过4次才这样表示)这种方式来表示,这对于SQ1和SQ2经常出现的大段大段连续的0来说能节省不少存储空间,具体游程方案看上面博客内容吧。
      好了,请问对最后这个存储码元信息的码元长度序列CCL怎么处理,要不要再来次Huffman编码,你可以试一试,反正PK没有再用Huffman折腾了(CCL中的值都不超过7了,也才19个数,就歇歇吧),直接对序列中19数每个分3bit来记录码元长度(不超过7,当然可以),19个数(19种码元长度)可能也不会全部出现,没有出现的CCL值为0,那么这19*3bits居然还可以省!省掉后面的0!将19个数顺序打乱,最可能为0的放到序列末尾,比如长度很长或很短的码元,15、1、14、2之类的可能不会出现,其CCL值就为0,而16、17、18基本不可能为0,就放到序列前面,看到这里,我都差点要崩溃了,这能省几个Bytes啊,PK你是处女座的吗!要逼死人啊!
      终于编码结束了,如何解码呢,根据以上所述,先是根据CCL(编码时要指定长度,因为不一定为19*3bit)解出SQ1和SQ2编码所用的码元,根据这个码元和SQ1游程编码的数据解出SQ1(编码时要指定长度,因为SQ1不一定为285个数),再根据这个码元和SQ1游程编码的数据解出SQ2(编码时要指定长度,因为SQ2不一定为30个数)。好,现在有了SQ1和SQ2就可以解出其代表的码元,根据这两个码元进而对原始文件内容压缩后的数据进行Huffman译码,先译285(字符数+重复内容长度区间数)这个信息,不超过256就是原始数据,超过了就是重复内容长度信息,需要接着译出重复内容位置信息,根据已译出的内容查询即可解出代表的内容,依次类推直到文件结束。
      顺便说一下,ZIP大概能压缩至源文件50%~70%大小,毕竟无损压缩,不错了!
      博客对ZIP改进方案进行了展望,专业做编解码的可以深入一下!
      补充一下,在编解码时要用到的主要算法:首先是统计重复内容上要用到模式匹配算法,尽可能寻找最长的重复内容;然后就是Huffman编码、同时根据码元长度序列恢复Huffman码元也是要一个算法(二叉树的存储与恢复);编码时还有个游程编码。这几个算法在常见的算法书里都有,不算很难,有兴趣的话可以自己写一个ZIP编解码程序。

0 0
原创粉丝点击