文件压缩
开发平台:Visual Studio 2008
开发技术:哈夫曼树,堆排序
项目流程:
(1)统计要压缩的文件中各字符出现的次数,利用数据结构中的小堆来构建Huffman树,每次在堆顶选出两个出现次数较小的字符先进行构建,将它们相加的结果又放进堆里面,直到堆里面的数据被取完,这样字符出现次数多的离Huffman树的根节点就比较近,字符出现次数少的离Huffman树的根节点就比较远。
(2)根据构建好的Huffman树写出Huffman code,规定向左为“0”向右为“1”,这样字符出现次数多的Huffman code较短,字符出现次数少的Huffman code较长。
(3)根据Huffman code 对原文件进行压缩,因为Huffman code刚好为“0”、“1”序列,所以可以每8位为一个字节写进压缩文件中,当余下的位数不够8位写不成一个字节时,需要在后面补0够一个字节再写入,这样就完成了文件的压缩,但要注意后面补0的位数为文件解压缩留下了隐患。
(4)在上个步骤压缩时由于后面不够一个字节在后面补了0,但是在解压缩的时候我们并不知道在进行压缩的时候在后面到底补了多少位0,而且在进行解压缩的时候我们还是需要用到Huffman树的结构,这时候我就想到用一个配置文件来记录原文件字符出现的总次数和每个字符分别出现的次数,这样在解压缩的时候直接去配置文件中读取需要的信息即可。
(5)读取配置文件中存放的信息,再次构建Huffman树,读取压缩文件,从huffman树的根节点开始找,每次遇到叶子结点就停止,将叶子结点所对应的字符写进解压缩文件,注意这里不能将压缩文件全部读完,只需要还原配置文件中所记录的总的字符个数个即可,这就完成了文件的解压缩。
背景知识:
Huffman树,又称为最优二叉树,是加权路径最短的二叉树,权值较大的结点离根节点较近。
路径:树中一个节点到另一个节点之间的分支构成这两个节点之间的路径。
路径长度:路径上的分支数目称作路径长度。
树的路径长度:从树的根节点到每一个节点的路径长度之和。
结点的带权路径长度:在一棵树中,如果其节点上附带有一个权值,通常把该节点的路径长度与该节点上的权值之积称为该结点的带权路径长度。
贪心算法:是指在问题求解时,总是做当前看起来最好的选择,也就是说贪心算法做出的不是整体最优的选择,而是某种意义上的局部最优解。
- 使用贪心算法构建Huffman树:
思想:每次从数组中取出两个当前权值最小的数去创建,并作为叶子结点,它们的根节点的权值是两者之和,把它们的根节点再放回数组,再选出当前数组中两个权值最小的数创建,直到数组的数选完。
- 根据Huffman树生成Huffman编码
可以看到,权值越小的,它的Huffman编码越长,权值越大的,它的Huffman编码越短。那么如何运用到文件压缩中呢?
我们来分析一个文件,假设一个文件的内容是“abbcccdddd”,“a”出现的次数是1次,“b”出现的次数是2次,“c”出现的次数是3次,“d”出现的次数是4次,现在以各字符出现的次数构建一个Huffman树,并为各字符编码,如下图所示:
写出每个字符所对应的Huffman编码:
‘a’:100 ‘b’:101 ‘c’:11 ‘d’:0
根据每个字符的huffman编码对照原文件字符出现的顺序写出Huffman code:1001011011111110000
这里的0或者1代表一个二进制位,那么压缩文件是多大呢?
我们可以看到,Huffman code 一共19个比特位,每8位为一个字节写入压缩文件,当余下的不够一个字节时,在后面补0够一个字节再写入,这里需要补上5个0总共3个字节,而原文件是10个字节,很明显少了7个字节,这就是文件压缩的原理。
项目主要思路:
1.统计:首先读取一个文件,统计出256个字符中各字符出现的次数以及字符出现的总次数;
2.建树:按照字符出现的次数,并以次数作为权值建立huffman树,根据所构建的huffman树生成huffman编码;
3.压缩:再次读取原文件,按照该字符对应的编码压缩文件;
4.加工:将字符出现的总次数以及各个字符出现的次数写进配置文件;
5.解压:利用压缩文件以及配置文件恢复原文件;
6.测试:首先对压缩和解压缩两个功能进行测试,看压缩后的文件是否比原文件小,以及解压缩文件与原文件的大小一致;在这个基础上再验证解压缩文件与原文件的内容是否保持一致,这里采用 beyond compare 软件来对文件内容进行验证,这样才验证了程序的大概功能是正确的。其次是压缩不同大小的文件计算出压缩所需要的时间来考量压缩的性能;
7.计算:经过多次压缩不同大小的文件不同类型的文件来计算文件的压缩率;
8.时间:使用 GetTickcount 函数计算完成压缩或者解压缩所需要的时间,GetTickcount 函数它返回从操作系统启动到当前所经过的毫秒数,常常用来判断某个方法执行的时间。
项目源码:https://github.com/YPT-victory/FileCompress.git
运行结果:
测试用例:
下面是对于不同类型的文件进行压缩和解压缩的文件详情:
由于beyond compare 对于图片文件和音频文件不识别,这里只能通过人工的方法进行验证,因为在进行解压缩的时候加了后缀信息,所以在进行验证的时候需要将后缀修改为和原文件类型相同的后缀再去打开,通过验证得出结论:图片文件和音频文件也能够正常压缩和解压缩。
思考:
- 为什么要使用配置文件?
答:(1)在项目中,有这样一个步骤就是将字符对应的编码转化为位,每8位转换为一个字节写入压缩文件,在转换过程中可能余下的位数不够8位,所以就需要在其后面补0将其填充至一个字节再写入,这里所补的0的个数是不固定的,为了在解压缩的时候能够正确的解压缩,所以就需要将原文件中字符的总个数记录下来以供解压缩使用。
(2)还有一点就是在解压缩的时候光有解压缩文件和字符出现的总个数是不够的,还需要用到Huffman树的结构,要构建Huffman树就需要用到每个字符出现的次数,而每个字符出现的次数需要遍历原文件才能获得,但是一般在解压缩的时候原文件是不存在的,所以就想到把每个字符出现的次数也写进配置文件里,在进行解压缩的时候去配置文件中获取该信息重新构建Huffman树即可。
还会持续更新~~~