在前几天写了哈夫曼树以及哈夫曼编码的博客:
http://blog.csdn.net/wenqiang1208/article/details/77261906
文件压缩
文件压缩的主要思想是利用哈夫曼编码来实现的,但是得到编码之前我们需要构建这棵树。那么利用什么来构建树呢?!这里,我们需要统计每个字符出现的次数,用次数来构建Huffman-Tree。假设我们现在有一个.txt的小文件,内容是”aaaabbbccd”。字符存在计算机中时以字节为单位的,因此我们需要将这些字符压缩成0、1表示的编码,0和1表示字节中的“位”,这样能大大降低文件的大小。
源文件的的大小10个字节,
压缩后一共19个比特位,不足1个字节后面补足0,一共3个字节。
为了实现文件压缩,需要对文件字符信息进行定义
字符信息结构体
包括字符,字符个数,以及字符压缩后的编码。
struct FileInfo
{
FileInfo()
: _count(0)
{}
/*FileInfo operator+(const FileInfo& FileInfoRight)//会改变原来的值
{
this->_count += FileInfoRight._count;
return *this;
}*/
FileInfo operator+(const FileInfo& FileInfoRight)
{
FileInfo temp(*this);
temp._count += FileInfoRight._count;
return temp;
}
bool operator <(const FileInfo& FileInfoRight)const
{
return _count < FileInfoRight._count;
}
bool operator >(const FileInfo& FileInfoRight)const
{
return _count > FileInfoRight._count;
}
bool operator != (const FileInfo& FileInfoRight)const
{
return _count != FileInfoRight._count;
}
std::string _strCode;//存放当前字符压缩后的编码
unsigned char _ch;//当前字符
size_t _count;//统计字符个数
};
对文件信息进行压缩时,需要在新文件中写入头部信息,因为进行解压时需要文件信息。
头部信息包括下面三个部分:
扩展名:(就是源文件的后缀名)
编码行数
编码行 (字符:字符个数)
实现文件压缩的步骤:
(1)打开文件,对文件字符信息进行统计。CountFileInfo函数
(2)根据字符出现的个数,构建哈夫曼树,后序遍历哈夫曼树获取字符编码。FillCode函数和_GenerateHuffmanCode函数
(3)进行文件压缩,写入新的文件中;
a、先把头部信息写入新文件的首部(因为进行解压时,需要用到信息)。WriteHead函数
b、遍历源文件,将每个字符的编码信息写入新文件中。CompressCore函数
文件解压
文件解压,根据文件压缩文件,进行还原源文件。
(1)先读取头部信息,重新构建哈夫曼树。
(2)根据字符编码信息,对哈夫曼树进行遍历,叶子结点信息就是字符信息。
(3)将叶子结点信息重新写入文件中。
文件解压时遇到的问题
(1)比如说“aaa”并没有构建哈夫曼树,那么并没有写入编码信息,
解决方法:直接写入字符信息,不需要转码
(2)压缩信息后面 补足的0,可能会被误读成压缩信息。
压缩以后的文件如果不够一个字节大小,会用0来代替空缺的比特位,这样带来的问题就是可能会多解压出字符。以上述的”aaaabbbccd”为例,压缩以后的编码为19比特位:00001111 11110110 100,计算机存储的最小单位为字节,因此这些编码会被存储为00001111 11110110 10000000,后边的五个0对我们来说就是多余的,如果不处理,就会多还原出5个a。
解决方法:给出一个charCount来统计所有字符串的个数,其实也就是根节点的值,每次还原出一个字符后,charCount就-1,直到charCount为0时说明所有字符都已经解压完成。
代码地址
https://github.com/WenQiangW/FileCompress
测试
使用BeyondCompared软件进行对比源文件和新还原的文件,看是否还原成功。
在huffman文件压缩下:
压缩一个1.2M左右的文件,能够到压缩0.9M左右,用时0.5秒左右。
压缩一个2.3M左右的文件,能够到压缩1.8M左右,用时0.8秒左右。
压缩一个8.3M左右的文件,能够到压缩6.7M左右,用时3秒。