本文采用哈夫曼编码的方式进行文件(文本文件)压缩和解压缩,首先介绍项目的整体思路:哈夫曼编码压缩文件实际就是统计出文件中各个字符出现的频率,然后为每个字符生成对应的编码,然后将每个字符用哈夫曼编码的形式按字节保存在压缩文件中。而文件的解压缩实际上就是将压缩文件翻译过来保存到解压缩文件中,需要使用压缩过程中生成的配置文件配合完成。下面将具体介绍文件的压缩和解压缩步骤:
文件的压缩的核心是产生哈夫曼编码,而哈夫曼编码的过程中需要找到一系列数据中的最小权重和次小权重,我们自然联想到用堆这种结构时间复发度小并且很方便找到最小值和次小值,我将堆的源代码放在Heap.h文件中(见下文)。现在我们进行文件压缩。
1 . 统计文件中所有字符的出现次数。 由于Ascall码字符一共255个,只有前128个字符可以显示,定义字符变量时一定要定义成无符号型变量 unsigned char ch ,这是ch读不到文件的结束标志,所以我们可以用函数feof来代替文件的结束标志EOF,最重要的是文件的打开方式一定要是二进制的形式打开否则读不到汉字字符,将出现乱码。关于存储方式我们采用哈希表的方式将每个字符映射为哈希表的下标,这样可以很方便的将每个字符和出现的次数对应起来。需要说明的是我们这个哈希表存的绝不是单纯的次数而是结点FileInfo,这个节点称为权重结点中保存出现次数和字符以及将来我们产生的哈夫曼编码,方便我们进行索引。
bool Compress(const char *filename)//该函数起到统计的作用
{
FILE *fout = fopen(filename, "rb");//以二进制形式打开文件
assert(fout);
unsigned char ch = fgetc(fout);
while (!feof(fout))
{
_Infos[ch]._count++;//统计各种字符在文件中的个数
ch = fgetc(fout);
COUNT++;//统计文件中总的字符个数
}
fclose(fout);
return true;
}
2 . 现在我们创建一个最小堆,将统计到的结点压入堆中;
3 . 从堆中取数据在HuffMan.h头文件中建立哈夫曼树;
4 . 通过哈夫曼树产生哈夫曼编码存入节点中;
5 . 遍历待压缩文件将对应的哈夫曼编码按字节保存到压缩文件中;
6 . 将每个字符出现的个数保存到配置文件中。由步骤5产生的压缩文件,当我们遍历到文件的最后一个字符时,如果编码凑不成8位一个字节我们给剩下的位置补0,为了解决最后一个字符的解析问题,我们将待压缩文件中的总的字符个数统计出来存到配置文 件的第一行,以后每行以“X,n”的形式存放字符和对应的出现次数。这样我们的文件压缩过程就完成了。
文件的解压缩思路简单,但是要尤其注意细节读配置文件就要花些心思,体现在换行符的统计上,下面进行文件的解压缩(源文件见Uncompress.h):
1 . 读配置文件;
2 . 通过配置文件重构哈夫曼树;
3 .文件的解压缩,按字符读入编码通过编码在哈夫曼树种寻找对应的字符,并将字符存入到解压缩文件中去,通过配置文件中读入的COUNT来控制最后一个字符正确编码的起止。这样文件的解压缩完成。
Heap.h
#include <vector>
template<class T>
struct Less
{
bool operator() (const T& l, const T& r)
{
return l < r; // operator<
}
};
template<class T>
struct Greater
{
bool operator() (const T& l, const T& r)
{
return l > r; // operator>
}
};
template<class T, class Compare=Less<T>>//哈夫曼结点的仿函数
class Heap
{
public:
Heap()
{}
Heap(const T* a, size_t size)
{
for (size_t i = 0; i < size; ++i)
{
_arrays.push_back(a[i]);//将所有数据插入堆
}
// 建堆
for (int i = (_arrays.size() - 2) / 2; i >= 0; --i)
{
AdjustDown(i);//对这个范围的每个节点都向下调整,建堆的过程实际就是向下调整堆的过程
}
}
void Push(const T& x)
{
_arrays.push_back(x);
AdjustUp(_arrays.size() - 1);//插入节点的过程实际就是向上调整堆的过程
}
void Pop()
{
assert(_arrays.size() > 0);
swap(_arrays[0], _arrays[_arrays.size() - 1]);
_arrays.pop_back();
AdjustDown(0);
}
T& Top()
{
assert(_arrays.size() > 0);
return _arrays[0];
}
bool Empty()
{
return _arrays.empty();
}
size_t Size()
{
return _arrays.size();
}
void AdjustDown(int root)
{
int child = root * 2 + 1;
Compare com;
while (child < _arrays.size())
{
// 比较出左右孩子中小的那个
if (child + 1<_arrays.size() &&
com(_arrays[child + 1], _arrays[child]))
{
++child;
}
if (com(_arrays[child], _arrays[root]))
{
swap(_arrays[child], _arrays[root]);
root = child;
child = 2 * root + 1;
}
else
{
break;