上一篇我们对自己实现的基于GZIP的压缩算法进行测试,对于有些文件压缩效率还是可观的,不过,直接采用huffman压缩思想却存在一定缺陷。
1.缺陷:
①需要创建huffman树,如果不同种类字节出现比较多的情况下,huffman树会很大。
比如:待压缩文件中有250个不同字符种类(一个字节能表示最多256个不同的字节),该250个字节都在huffman树叶子节点的位置,那么将来会有(250 + 249)个节点,占用内存较大,且获取编码的效率低(递归获取)
②huffman树如果比较大,解压缩效率低。由于解压缩时不断从根节点往叶子节点方向移动,重复次数多。
③为了能够解压缩,压缩完成后必须在压缩文件中保存字符频度信息。如果待压缩中间字节不同种类多,出现次数比较均匀,那么压缩后字符频度信息表将会占很大空间,影响压缩bu比率。
改进措施:不要直接采用huffman树,推荐采用范式huffman树
什么是范式huffman树:范式huffman树是在huffman树的基础之上,进行了一些强制性的约定,即:对于同一层节点中,所有的叶子节点都调整到左边,然后,对于同一层的叶子节点按照符号顺序从小到大调整 ,最后按照左0右1的方式分配编码 参考博客
此过程可大大降低编码复杂度,根据特性即可计算出每个叶子节点的编码。不过在计算叶子节点编码之前,需要拿到每个叶子节点的码字长度(该叶子节点在树中的高度)
2.范式huffman树的创建:
①模拟Huffman树的创建(采用静态数组来模拟huffman树)
遍历压缩文件,统计每个字符出现的频度,然后采用数组的方式模拟huffman树。
采用静态数组构建huffman树
范式huffman树和普通huffman树最终获取的每个字节的编码长度实际是一样的
采用范式huffman树的优点
①只需要用数组模拟出huffman树,节省内存空间
②用范式huffman树获取编码的效率更快,不需要再去遍历huffman树
③关于压缩数据的保存,不需要保存频度,只需保存编码长度
将256个字符种类用前256个字节来保存编码长度
比如:········000024340001232000········
每个字符对应位置保存编码长度,没有的就用0表示。
下面又引入新的问题?如何只通过编码长度就能获取字符编码
解决方案:在上述方式的基础上再进行了压缩-----游程编码
3.什么是游程编码?
例: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,GZIP认为重复出现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
参考文档
4.基于Huffman编码的压缩和解压缩
① 从压缩数据中获取符号的编码位长,构建符号位长表。
②根据编码位长建立解码表。(根据范式huffman树的特点,前文讲过)
③解码
5.距离的压缩
LZ77在查找缓冲区中找匹配时,最长的距离不会超过32K,即最大的距离为32768,即距离的范围是[1,32768],距离会非常多,虽然不会达到3276个,但是如果对于一个比较大的文件进行LZ编码,distance上千还是很正常的,因此会导致huffman树非常大,计算量和内存消耗都会超过当时的硬件条件,怎么办呢?
GZIP提供了一种非常好的方式,将distance划分成多个区间,每个区间当做一个整数来看,该整数称为Distance Code。当一个distance落到某个区间,则相当于出现了那个Code,虽然distance很多,Distance Code可以划分少一点,即多个distance对应一个Distance Code,最后只需要对Distance Code
进行huffman编码即可。得到Code后,Distance Code再根据一定规则扩展出来。GZIP最终将distance划分成了30个区间
6.原字符和长度的压缩
原字符表示在LZ77中未匹配的字符,长度表示重复字符串的个数,都占了一个字节,因此GZIP将其压缩合二为一了,即对于原字符和距离采用同一棵huffman树进行处理。原字符的范围是[0, 255],距离是[3, 258],如何进行处理呢?
GZIP用整数0~255表示原字符,256表示结束标志,即解码以后是256表示解码结束,从257开始表示距离,比如:257表示重复3个字符,258重复4个字符,但GZIP并没有一直这么一一对应,而是采用了和distance
类似的方式进行分区,总共将距离划分成了29个区间.
笔者能理解就这么多,有兴趣可以自己在网上找资料。