前言
- 项目采用Huffman编码的方式进行文件压缩与解压缩。主要原理是通过Huffman编码来表示字符,出现次数多的编码短,出现次数少的编码长,这样整体而言,所需要的bit位是减少的,就实现了文件压缩功能。
- 读取文件中的字符出现次数,构建Huffman树,然后解析这个字符的每一位,遇到一个叶子结点,就代表还原了一个字符,这时就将这个字符写到解压缩文件里。
- 注意当大部分字符出现的频率都差不多时,Huffman压缩的效率比较低。
一、文件压缩简介
1、什么是文件压缩
文件压缩是指在不丢失有用信息的前提下,缩减数据量以减少存储空间,提高其传输、存储和处理效率,或按照一定的算法对文件中数据进行重新组织,减少数据的冗余和存储的空间的一种技术方法。
2、压缩的本质及好处
- 压缩的目的是让文件变小,减少文件所占的存储空间
- 紧缩数据存储容量,减少存储空间
- 可以提高数据传输的速度,减少带宽占用量,提高通讯效率
二、 huffman树简介
1、什么是huffman树?
哈夫曼树是一种带权路径长度最短的二叉树,也称为最优二叉树。
带权路径长度: 从二叉树的根结点到二叉树中所有叶结点的路径长度与相应权值的乘积之和为该二叉树的带权路径长度WPL。
如下图:
- WPLa = 1 * 2 + 3 * 2 + 5 * 2 + 7 * 2 = 32
- WPLb = 1 * 2 + 3 * 3 + 5 * 3 + 7 * 1 = 33
- WPLc = 7 * 3 + 5 * 3 + 3 * 2 + 1 * 1 = 43
- WPLd = 1 * 3 + 3 * 3 + 5 * 2 + 7 * 1 = 29
将带权路径最小的二叉树称为huffman树,所以图(d)为Huffman树。
2、huffman树构建
由给定的n个权值{ w1, w2, w3, … , wn}构造n棵只有根节点的二叉树森林F={T1, T2 , T3, … ,Tn},每棵二叉树Ti只有一个带权值wi的根节点,左右孩子均为空。
重复以下步骤,直到F中只剩下一棵树为止
1)在F中选取两棵根节点权值最小的二叉树,作为左右子树构造一棵新的二叉树,新二叉树根节点的权值为其左右子树根节点的权值之和
2)在F中删除这两棵二叉树
3)把新的二叉树加入到F中
如图所示:
3、获取huffman编码
三、huffman树和文件压缩的关系
压缩的目的是让文件变小,减少文件所占的存储空间。那怎么才能让文件变小呢?
基于huffman树的文件压缩是从字节方面来进行压缩的,一个字节占用8个比特位,如果能够给一个字节找到更短的编码,即少于8个比特位,就可以起到压缩的目的,编码一般分为:静态等长编码和动态不等长编码。
比如:ABBBBCCCCCDDDDDDD字符串(16个字符占16个字节)
- 静态等长编码:
文件中共有4个不同种类的字符,因此每个字符可以用两个二进制的比特位表示。
用等长编码对上述源数据进行压缩:01101110 11110111 11100011 10011110,压缩完成后的结果只占4个字节,压缩率还是比较高的。
- 动态不等长编码
四、 文件的编写思路及流程
1、文件的压缩
-
打开被压缩文件,获取文件中每个字符串出现的总次数。
-
以每个字符出现的总次数为权值构建 huffman 树。
-
通过 huffman 树获取每个字符的 huffman 编码。
-
读取源文件,对源文件中的每个字符使用获取的huffman编码进行改写(要注意当最后一个字节不满时,需要单独处理 ),将改写结果写到压缩文件中, 直到文件结束,生成一个新的压缩文件。
我们可以想一下,当文件压缩完成后随之字符的相关信息也会失效,那么怎样解压呢,没有字符信息就没办法构建对应的哈夫曼树,也就没办法进行解压,因此:压缩文件中除了保存压缩数据外,还必须保存解压缩所需的信息。如文件的类型、每个种字符及出现的次数,当然这些信息最好写在文件的开头,方便我们进行读取。所以得规定压缩文件的格式来获取字符相关信息以帮助我们对文件进行解压缩。
压缩文件格式:
2、文件的解压缩
-
从压缩文件中获取源文件的后缀来判断文件的类型能否解压。
-
从压缩文件中获取字符次数的总行数
-
获取每个字符出现的次数
-
重建huffman树
-
-
解压压缩数据
- a.从压缩文件中读取一个字节的获取压缩数据ch
- b.从根节点开始,按照ch的8个比特位信息从高到低遍历huffman树: 该比特位是0,取当前节点的左孩子,否则取右孩子,直到遍历到叶子节点位置,该字符就被解析成功。将解压出的字符写入文件。如果在遍历huffman过程中,8个比特位已经比较完毕还没有到达叶子节点,从a开始执行。
- c.重复以上过程,直到所有的数据解析完毕。
五、相关代码链接
基于huffman树的文件压缩与解压缩
六、关于huffman树的问题及思考
1、解压缩时解压不完全。由于使用文本形式读取压缩文件,有可能提前遇到文件结束标志,所以要改为二进制形式读写。二进制形式是读取二进制编码。如果以文本形式读取的话,回车会被当成一个字符’\n’,而二进制形式则会认为它是两个字符即’\r’回车、’\n’换行;如果在文本形式中遇到0xFF的话,文本形式会认为这是文本结束符,而二进制则不会对它产生处理。
2、换行问题---因为在对文件进行压缩的时候要保存字节频度信息,保存时用换行符来表示每个信息的结束,所以遇到换行符会进行break,不保存其信息,但是在通过字符品读信息进行解压缩的时候,会读取到换行符,需要对换行符进行单独处理,否则解压缩结果就会错误。
3、压缩汉字时出现问题,因为汉字是由多个字符表示,这些字符的范围是0—255,所以在结构体中要用unsigned char表示。
4、创建huffman树时,因为节点中保存的是一个结构体而不是一个简单的内置类型,因此在对节点进行“比较”操作的时候需要自己重载这些比较操作符。
5、引用问题 ,生成编码的时候 这块加引用就是为了方便,修改chCode就是在修改 _fileInfo[root->_weight._ch]._chCode,主要将生成的编码保存到_fileInfo[root->_weight._ch]._chCode中,压缩的时候才能取到编码信息,即编码的长度不会为0。
6、第一次在对源文件进行huffman树构建的时候,需要对源文件从头读完,所以文件指针就在文件末尾,在第二次需要对源文件进行压缩的时候,需要从头开始进行字符及其对应编码的替换,所以得把文件指针移到文件开头,要不然压缩结果就有问题。
7、huffman树压缩完成后,必须要在压缩文件中保存字节频度信息,才可以解压缩,如果字节的频度信息比较大,对压缩率也会有一定的影响。而且解压缩时通过不断遍历还原的huffman树来进行解压,对解压的效率 也会有一定的影响。
8、我们在借助Huffman树去生成编码的时候时候会存在前缀编码呢?答案其实是不会的,因为这些权值其实都是处在叶子节点的位置上的,所以说不会有一个结点是另一个结点的前缀,因为只有一个结点在另一个结点的前缀路径中才会是的一个的前缀是另一个的前缀,所以借助Huffman树是不会出现这种情况的。
七、测试
-
用不同格式的文件测试程序,用beyond compare进行对比是否正确。(注:.hf后缀是压缩后的文件,-huff.文件类型 是对压缩文件进行解压后的文件) 文件压缩与解压缩项目目录
- doc源文件、压缩文件以及解压缩文件源文件、压缩文件以及解压缩文件大小对比用beyond compare对比源文件与解压文件是否相同---所有参数都一样
- jpg源文件、压缩文件以及解压缩文件源文件、压缩文件以及解压缩文件大小对比用beyond compare对比源文件与解压文件是否相同---所有参数都一样
- md文件源文件、压缩文件以及解压缩文件源文件、压缩文件以及解压缩文件大小对比用beyond compare对比源文件与解压文件是否相同---所有参数都一样
- txt文件源文件、压缩文件以及解压缩文件源文件、压缩文件以及解压缩文件大小对比用beyond compare对比源文件与解压文件是否相同---所有参数都一样
- docx文件源文件、压缩文件以及解压缩文件源文件、压缩文件以及解压缩文件大小对比用beyond compare对比源文件与解压文件是否相同---所有参数都一样
-
性能分析
- 压缩率 = (压缩后文件大小 / 原始文件大小) * 100%
- 压缩比例 = 原始文件大小 / 压缩后文件大小
- 节省空间比例 = (原始文件大小 - 压缩后文件大小) / 原始文件大小 * 100%
文件名 | 源文件大小 | 压缩文件大小 | 解压缩后文件大小 | 压缩率 | 压缩比例 | 节省空间比例 |
1.txt | 1kb | 1kb | 1kb | 100% | 1 | 0% |
2.md | 4kb | 3kb | 4kb | 75% | 0.750 | 25% |
3.docx | 16kb | 17kb | 16kb | 106.3% | 1.063 | 0% |
docfile.doc | 28kb | 12kb | 28kb | 42.9% | 0.429 | 57.1% |
imagefile.jpg | 39kb | 41kb | 39kb | 105.1% | 1.051 | 0% |
-
对压缩文件进行二次压缩
文件名 | 原压缩文件大小 | 二次压缩后文件大小 |
1.hf | 1kb | 1kb |
2.hf | 3kb | 2kb |
3.hf | 17kb | 2kb |
docfile.hf | 12kb | 2kb |
imagefile.hf | 41kb | 2kb |
对压缩文件进行多次压缩
文件名 | 原压缩文件大小 | 三次压缩后文件大小 |
1.hf | 1kb | 1kb |
2.hf | 2kb | 2kb |
3.hf | 2kb | 2kb |
docfile.hf | 2kb | 2kb |
imagefile.hf | 2kb | 2kb |
文件名 | 原压缩文件大小 | 四次压缩后文件大小 |
1.hf | 1kb | 1kb |
2.hf | 2kb | 2kb |
3.hf | 2kb | 2kb |
docfile.hf | 2kb | 2kb |
imagefile.hf | 2kb | 2kb |
-
总结:
- 1、Huffman压缩适用于字符出现次数差值较大,分布不平均的文件。
- 2、对于文件多次压缩意义不大。
- 3、不能压缩文件夹,只能对单个文件进行压缩和解压缩。
- 4、从测试来看,能够处理大部分文件较好性能的压缩。