本文采用哈夫曼编码的方式进行文件的压缩和解压缩,主要原理是通过huffman编码来表示字符,出现次数多的编码短,出现次数少的编码长,这样整体而言,所需的总的bit位是减少的。但是当大部分字符出现的频率都差不多时,huffman压缩的压缩率就会很低。
哈夫曼编码压缩文件,主要思想:
(1).统计出文件中各个字符出现的次数;
(2).构建huffmanTree,生成每个字符对应的编码,然后将编码写入压缩文件中;
(3).编写配置文件,因为在压缩后是找不到原文件的,所以需要借助配置文件来记录文件出现的字符以及字符出现的次数,方便解压缩;
(4).文件的解压缩实际上就是将压缩文件翻译过来保存到解压缩文件中,需要使用压缩过程中生成的配置文件配合完成。
2.下面具体介绍文件的压缩和解压缩步骤.
首先是文件压缩:
1) 统计文件中所有字符出现的次数,需要明白以下几点:
1.1)在文本文件中,数据是以字符的ASCII码形式存放,ASCII码的范围是0-255,所以文件压缩中以256的数组作为底层数据结构,其中数据类型为CharInfo,包括字符,字符出现次数以及huffman编码;
1.2)在读写文件时要用二进制形式,以”r”,”w”和“rb”,“wb”来进行读写,它们主要的区别在哪呢?
<1>从文件编码的方式来看,文件可分为ASCII码文件和二进制码文件两种。ASCII文件也称为文本文件,这种文件在磁盘中存放时每个字符对应一个字节,用于存放对应的ASCII码;
<2>二进制文件是按二进制的编码方式来存放文件的。例如,数5678的存储形式为:00010110 00101110只占二个节。
<3> 在这里以文本形式读取压缩文件,有可能提前遇到文件结束标志。二进制形式是读取二进制编码,如果以文本形式读取的话,回车和换行会被当成一个字符'\n',而二进制形式则会认为它是两个字符即'\r'回车、'\n'换行;如果在文本形式中遇到0x1B的话,文本形式会认为这是文本结束符,而二进制模型则不会对它产生处理,所以这里需要以"rb","wb"的方式才能正确读写文件。
1.3) 由于ASCII码字符一共256个,只有前128个字符可以显示,定义字符变量时需要定义成unsigned char 型,在读取文件时,如果用while(ch!=EOF)来判断,ch有可能读不到文件的结尾就提前结束,所以我们采用函数feof来代替文件的结束标志EOF.
说明:EOF的16进制为0xFF(十进制为-1),特用在文本文件中,因为在文本文件中数据是以ASCⅡ码值的形式存放,普通字符的ASCⅡ码的范围是32到127(十进制),与EOF不冲突,因此可以直接使用;但是在二进制文件中,数据有可能出现-1,因此不能用EOF来作为二进制文件的结束标志,可以通过feof函数来判断。也就是说feof这个函数可用于判断文件是否结束(包括文本文件和二进制文件)。
2)构建huffman树.
通过建一个小堆,将统计到count !=0的结点压入堆中,从堆中取最小数据以及次小数据,将它们的和作为哈夫曼树的权值节点,构建到huffman树中;
这里说明一下,在构建huffman树的时候,如果将256个字符全部构建进去,不仅会降低效率还将占用很大空间,所以引入非法制的概念,只将次数不为0的节点构建到huffmanTree中去。
3)通过哈夫曼树产生哈夫曼编码;
规则是:从根节点出发,往左走-->0 ,往右走-->1,遇到叶子节点的情况,就将它对应的huffman编码写入数组中。
4)从原文件得到字符,将对应的哈夫曼编码每满8个字节就写入压缩文件中,如果最后一个字节不满8位,用0填充;
5)编写配置文件.
由于在解压时往往是没有原文件的,而我们要解压的话必须要知道这棵huffman树,所以在压缩的时候需要编写一个配置文件来存储huffman树的信息(各个字符以及字符出现的次数)。在配置文件里面将:字符+字符出现的次数存在一行。在这里要使用itoa这个函数 将次数转换成一个字符串(string)类型存储。
在压缩图片或音频的时候出现问题,主要是对fwrite和fputs的使用不明确.
fwrite(line.c_str(),1,line.size(),finConfig);
//fputs(line.c_str(),finConfig);
fputs是文本形式写入一个字符串,遇到‘\0’就停止写入; fwrite是二进制写,可以指定写入多少个字节.
2.文件的解压缩.
1)从配置文件中得到各种字符出现的次数;
1>读配置文件时空行的处理一定要注意,以及如何将字符串中存放的次数转换为数字,这里使用atoi函数。
使用string::substr(pos)函数提取字符出现的次数
_str[ch]._count = atoi(line.substr(2).c_str());
2>因为string底层是char*,而每一行的第一个字符是0—255,所以这一块我们要进行处理,可以单独读取。
2)重新构建huffman树;
3)在压缩文件里面读取一个字符,然后解析这个字符的每一位,只要遇到一个叶子结点,就代表还原了一个字符,这时候将该字符写到解压缩文件里去。但是在这里要注意,有可能最后的几位编码是我们补上去的,所以在这里我们要以原文件中字符出现的总次数来控制解压缩,根据huffman的性质可知,根节点的权重就是字符出现的总次数。
对于以上的用Huffman算法实现文件的压缩与解压缩,其源码已托管至github上: