压缩数据的概念
为什么要进行压缩
压缩的分类
ZIP压缩的历史
GZIP压缩算法的原理
项目测试
huffman树的缺陷及改进(范式haffman树)
huffman与LZ77结合及改进
项目改进
#压缩数据的概念
数据压缩是指在不丢失有用信息的前提下,缩减数据以减少存储空间,提高其传输,存储和处理效率。
按照一定的算法对数据进行重新组织,减少数据的冗余和寻出的空间的一种技术方法。
#为什么要进行压缩
1.紧缩数据存储容量,减少存储空间
2.提高数据传输的速度,减少宽带占用量,提高通讯效率
3.对数据的一种加密保护,增强数据在传输过程中的安全性
#压缩的分类
##有损压缩
允许压缩过程中损失一定的信息,解压缩之后不能将其还原成与源文件完全相同的格式,但是解压缩识别内容时基本没什么影响。
##无损压缩
源文件被压缩之后通过解压缩能够还原成与源文件内容和格式一样。
#ZIP压缩的历史
1977年由两个以色列人提出的基于重复语句层面的一种通用的压缩算法。
通用:对文件没有要求
#GZIP压缩算法的原理
LZ77压缩算法+Huffman编码压缩
LZ77压缩算法:对上下文中重复语句进行压缩
huffman编码思想:对压缩完成的数据z在字节上进行压缩
LZ77压缩算法
将重复语句替换成更短的<距离,长度>对。
LZ77之后,虽然基本没有语句层面的压缩,但是会存在字节上的重复
(LZ77详细介绍:https://editor.csdn.net/md/?articleId=104500235)
Huffman编码压缩
给待压缩中的字节重新找编码–出现次数越多的字节分配更短的编码,出现次数较少的字节分配较长的编码,然后用分配的编码对压缩中的字节进行重新改写
(Huffman详细介绍:https://editor.csdn.net/md/?articleId=104770419)
#项目测试
分别对文件,照片,音乐,视频进行压缩
测试对象 | 源文件大小 | 压缩文件大小 | 压缩比率 | 运行时间 |
---|---|---|---|---|
文本文件 | 19kb | 7kb | 37% | 较快 |
照片 | 18Kb | 15kb | 83% | 较快 |
音频 | 350kb | 385kb | 110% | 慢 |
对压缩结果进行二次压缩?二次压缩压缩率如何?为什么?
LZ77之后,压缩文件基本上没有重复语句,对LZ77压缩的结果继续采用LZ77方式进行压缩,基本不会达到什么效果,而且还会使压缩结果变大,因为要保存标记比特位。
#huffman树的缺陷及改进(范式haffman树)
能否直接采用huffman的方式对LZ77的结果直接压缩???
可以的,只不过压缩率可能不太好
直接采用Huffman思想LZ77结果的缺陷分析
直接用Huffman进行压缩缺陷
a.需要创建huffman树,如果文件中不同种类字节出现比较多的情况下,huffman树将会很大假设:待压缩文件中不同种类的字节有245个,该字节将来都在huffman树叶子节点的位置,那么huffman树将来总会有:245+244=489个节点(Huffman树中不存在度为1的节点,根据二叉树的特性:n0=n2+1)s
树占用的内存比较大,
获取编码效率太低(通过递归方式获取编码)b.huffman树如果比较大,解压缩效率也比较低
解压缩:不断从根节点往叶子节点的方向移动–重复次数非常多
c.为了能够解压缩,压缩完成之后必须在压缩文件中保存:字符频度信息(如果待压缩中字节不同种类比较多,出现次数比较均匀–加入245个字节)
字符频度信息表将会占有很大空间,而影响压缩比率
d.因为LZ77结果中除了保存压缩数据,还保存了压缩数据中每个字节的标记信息。(最差情况下没有找到重复,标记信息可以占到源文件的8分之1)
\n
如果采用huffman树的方式之间对LZ77的结果进行压缩,标记信息也会参与压缩过程,影响压缩率。标记信息不属于源文件(LZ77压缩之前的文件)中的信息,但现在也参与到huffman树的压缩,因此肯定会影响压缩率
#项目的改进–范式huffman树:
对于缺陷a,b,c优化方案:不要直接采用huffman树—>huffman树的变形-范式Huffman树
(既然树的压缩程度都一样,那就使用特殊的树,使记录的信息最少)
范式Huffman树只要知道符号的编码位长(节点在树中的高度),就可以推断出符号的编码
##什么是范式huffman树?
对于同一层节点中,所有的叶子节点都调整到左边,然后,对于同一层的叶子节点按照符号顺序从大到小调整,最后按照左0右1的方式分配编码。
从上表中可以得出一些结论:
- 只要知道一个符号的编码位长就可以知道它在范式树上的位置。即:码表中只要保存每个符号的编码长度(即节点在树中的高度)即可,其远远要比符号频度小。
- 相同位长的编码之间都相差1
- 第n层的编码可以根据上层算出来:code = (code + count[n-1])<<1;
范式huffman树该如何创建呢?
范式huffman树根本不用创建,可以利用huffman树推到出来:
- 对huffman树中的每个叶子节点求层数,得出huffman码表
- 对huffman码表按照:码长(节点在树中的高度)为第一关键字、符号为第二关键字进行排序
通过以上两步就可以得出范式huffman树的码表,然后按照上面的公式既可以计算出范式huffman码表。
##基于范式hufman树的压缩和解压缩
压缩:
1.计算出huffman码表,并推断每个字符的范式huffman编码
2.读取源文件,将源文件中的每个字节按照对应的范式huffman编码进行改写压缩文件的格式:
1.先保存各个字节对应的编码程度(huffman压缩中保存的是符号及符号出现的频率)
2.保存压缩数据解压缩
1.从压缩数据中获取符号编码位长,构建符号位长表
符号 | 位长 |
---|---|
A | 2 |
B | 5 |
C | 5 |
D | 2 |
E | 5 |
F | 5 |
G | 2 |
H | 3 |
2.根据编码位长建立解码表
序号 | 编码位长 | 首编码 | 符号数量 | 符号索引 |
---|---|---|---|---|
0 | 2 | 00 | 3 | 0 |
1 | 3 | 110 | 1 | 3 |
2 | 5 | 11100 | 4 | 4 |
编码:可以位长算出来,此处保存成数字
符号数量:通过map或者unordered_map来进行统计
符号索引:在符号位长表中的首次出现下标
#huffman与LZ77结合及改进
##1.距离的压缩
LZ11在查找缓冲区中找匹配时,最长距离不超过32k,即最大的距离32768,范围[1,32768],距离会非常多。但是对于一个比较大的文件进行LZ编码,会导致huffman树非常大,计算量和内存消耗会超过当前的硬件条件。
每一个距离对应一个码字,我们分析过:越小的距离,出现的越多;越大的距离,出现的越少,所以区间划分不是等间隔的,而是越来越稀疏。
自然是距离越小出现频率越高,所以距离值小的时候,划分密一些,这样相当于一个放大镜,可以对小的距离进行更精细地编码,使得其编码长度与其出现次数尽量匹配;对于距离较大那些,因为出现频率低,所以可以适当放宽一些。
另一个原因是,只要知道这个区间Code的码字,那么对于这个区间里面的所有distance,后面追加相应的多个比特即可。
比如,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
这样计算复杂度和内存消耗是不是很小了,因为需要进行Huffman编码的整数一下字变少了,这棵树不会多大,计算起来时间和空间复杂度降低,扩展起来也比较简单。
Phil Katz把distance划分为30个区间,,每个区间容纳distance的个数刚好是2的n次幂,huffman树只对0~29这30个Code进行编码,得到编码。
左边的Code表示区间的编号,是0-29,共30个区间,这只是个编号,没有特别的含义,但Huffman就是对0-29这30个Code进行编码的,得到区间的码字;
bits表示distance的码字需要在Code的码字基础上扩展几位,比如0就表示不扩展,最大的13表示要扩展13位,因此,最大的区间包含的distance数量为8192个。
##2.源字符和长度的压缩
源字符和长度(重复字符串的个数)都占了一个字节。因此GZIP将其压缩合二为一了,即对于原字符和距离采用同一棵huffman树进行处理。源字符的范围[0,255],距离[3,258].
GZIP用整数0~255表示原字符,256表示结束标志,即解码以后是256表示解码结束,从257开始表示距离,比如:257表示重复3个字符,258重复4个字符,但GZIP并没有一直这么一一对应,而是采用了和distance类似的方式进行分区,总共将距离划分成了29个区间,如下图:
即原字符和距离的huffman编码的输入元素一共有285个,当解码器接收到一个比特流的时候,首先可以按照literal/length这个码表来解码,
如果解出来是0-255,就表示未匹配字符,如果是256,那自然就结束,
如果是257-285之间,则表示length,把后面扩展比特加上形成length后,后面的比特流肯定就表示distance,因此,实际上通过一个Huffman码表,对各类情况进行了统一,而不是通过加一个什么标志来区分到底是源字符还是重复字符串。
GZIP的主体压缩过程
第一步:先是采用LZ77对源文件进行压缩,
第二步采用huffman对LZ77的压缩结果进行再次压缩.huffman码表1:原字符和长度使用一棵huffman树
huffman码表2:distance对应huffman树称为
而最终的huffman树信息只需要使用码字长度保存即可,称之为CL(Code Length),即两个码表长度分别为:CL1、CL2。码树记录下来,对原字符的编码比特流称为LIT比特流,对distance编码的比特流称为DIST比特流。按照上面的方法,LZ的编码结果就变成四块:CL1、CL2、LIT比特流、DIST比特流 。
##3.游程编码
编码的长度即CL也是一对数字,该部分信息理论也可以使用huffman树再次压缩,但是GZIP并没有对其使用huffman树进行压缩,而是使用了游程编码。
游程,即一段完全相同的数的序列。游程编码,即对一段连续相同的数,记录这个数一次,紧接着记录出现了多少个。比如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(二进制) 4, 16, 1, 3, 3, 3, 6, 16, 3, 16, 0, 17, 3, 2, 16, 0解析:16–代表重复 .17,18表示0 .0-15是CL可能出现的值
16后面跟着两个比特位 00->3个 01->4 10->5 11->6
17后面跟着3个比特分别记录长度为3-10(总共8种可能)的游程;
18后面跟着7个比特表示11-138(总共128种可能)的游程。4,4,4,4,4,这段游程记录为4,16,01,也就是说,4这个数,后面还会连续出现了4次(01)
6,16,11,16,00表示6后面还连续跟着6()个6,再跟着3个6;
…
原字符和长度的编码符号总共有286个(256个原字符+1个结束标记+29个长度区间),distance编码区间总共30个,因此这棵树不会特别深,huffman编码后的码字长度不会特别长,不会超过15,即树的深度不会超过15,因此CL1和CL2这两个序列的任意整数的值的范围是0-15,0表示没有出现,故GZIP对CL1和CL2使用了游
程编码。
项目代码实现:
https://github.com/uniquefairty/C-Code/tree/master/HuffmanTreeZip
参考文章:https://www.cnblogs.com/cliveleo/articles/9759019.html