本文采用哈夫曼编码的方式进行文件(文本文件)压缩和解压缩,首先介绍项目的整体思路:哈夫曼编码压缩文件实际就是统计出文件中各个字符出现的频率,然后为每个字符生成对应的编码,然后将每个字符用哈夫曼编码的形式按字节保存在压缩文件中。而文件的解压缩实际上就是将压缩文件翻译过来保存到解压缩文件中,需要使用压缩过程中生成的配置文件配合完成。下面将具体介绍文件的压缩和解压缩步骤:
文件的压缩的核心是产生哈夫曼编码,而哈夫曼编码的过程中需要找到一系列数据中的最小权重和次小权重,我们自然联想到用堆这种结构时间复发度小并且很方便找到最小值和次小值,我将堆的源代码放在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来控制最后一个字符正确编码的起止。这样文件的解压缩完成。
总结:
项目的特点和用到的技术有,仿函数,堆,哈夫曼编码技术,string类,哈希表
项目注意事项,文件名字转换方法艺术,文件的二进制读入写入,读配置文件时换行符的处理,以及统计的字符数如何以10进制字符的形式存到文件中去。其他问题详见源代码重点注释的地方。
Heap.h
#include
template
struct Less
{
bool operator() (const T& l, const T& r)
{
return l < r; // operator<
}
};
template
struct Greater
{
bool operator() (const T& l, const T& r)
{
return l > r; // operator>
}
};
template>//哈夫曼结点的仿函数
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;
}
}
}
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (Compare()(_arrays[child], _arrays[parent]))
{
swap(_arrays[parent], _arrays[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void Print()
{
for (size_t i = 0; i < _arrays.size(); ++i)
{
cout << _arrays[i] << " ";
}
cout << endl;
}
public:
vector _arrays;
};
//测试堆
//void Test1()
//{
// int a[10] = { 10, 11, 13, 12, 16, 18, 15, 17, 14, 19 };
// Heap > hp1(a, 10);
// hp1.Push(1);
// hp1.Print();
//
// Heap hp2(a, 10);
// hp2.Push(1);
// hp2.Print();
//
//
// Less less;
// cout< greater;
// cout<
HuffMan.h
#pragma once
#include "Heap.h"
template
struct HuffManNode
{
HuffManNode *_left;
HuffManNode *_right;
HuffManNode *_parent;
T _weight;
HuffManNode(const T&x)
: _left(NULL)
, _right(NULL)
, _parent(NULL)
, _weight(x)
{}
};
template
class HuffMan
{
typedef HuffManNode Node;
template
struct NodeCompare
{
bool operator() ( const Node*l, const Node*r)//模板不能分离编译
//因此用到NodeCompare的地方都要放到一个文件
{
return l->_weight < r->_weight;
}
};
protected:
Node* _root;
public:
HuffMan()
:_root(NULL)
{}
~HuffMan()
{}
public:
Node* GetRootNode()
{
return _root;
}
Node* CreatTree(T*a, size_t size,const T& invalid)
{
//取数转换成哈夫曼结点,放到最小堆中
assert(a);
Heap> minHeap;
for (size_t i = 0; i < size; ++i)
{
if (a[i] != invalid)
{
Node*node = new Node(a[i]);
minHeap.Push(node);
}
}
/*for (int i = 0; i<10; i++)
{
Node *temp = minHeap._arrays[i];//用于测试的代码
cout << temp->_weight << " ";
}*/
//从最小堆中取最小的和次小的结点,建立哈夫曼树
while (minHeap.Size()>1)
{
Node* left = minHeap.Top();//取最小的
minHeap.Pop();
Node* right = minHeap.Top();//取次小的
minHeap.Pop();
Node *parent = new Node(left->_weight + right->_weight);
parent->_left = left;
parent->_right = right;
left->_parent = parent;
right->_parent = parent;//链接节点间的关系
minHeap.Push(parent);//将最小的和次小的之和放到堆中重新调整
}
_root = minHeap.Top();//堆中最后剩下的结点就是哈夫曼的根结点
return _root;
}
HuffManNode* GetRoot()
{
return _root;
}
void PrintHuff()
{
Node *root = _root;
_Print(root);
}
protected:
void _Print(Node *root)
{
if (root == NULL)
{
return;
}
else
{
cout << root->_weight;
}
_Print(root->_left);
_Print(root->_right);
}
};
//void TestHuff()
//{
// int a[] = { 1, 0, 2, 3, 4, 5, 6, 7, 8, 9 };
// HuffMan t;
// t.CreatTree(a, sizeof(a) / sizeof(int), -1);
//
//}
filecompress.h
# include
# include
# include
# include
# include"HuffMan.h"
using namespace std;
typedef unsigned long long LongType;
struct FileInfo
{
unsigned char _ch;
LongType _count;
string _code;
FileInfo(unsigned char ch=0)
:_ch(ch)
, _count(0)
{}
FileInfo operator+(FileInfo filein)
{
FileInfo temp;
temp._count=_count + filein._count;
return temp;
}
bool operator<( const FileInfo filein)const
{
return _count < filein._count;
}
bool operator!=(const FileInfo Invalid)const
{
return _count != Invalid._count;
}
};
class FileCompress
{
protected:
FileInfo _Infos[256];
LongType COUNT = 0;
public:
FileCompress()
{
for (int i = 0; i < 256;i++)
{
_Infos[i]._ch = i;
}
}
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;
}
void GenerateHuffManCode()
{
HuffMan t;
FileInfo invalid;
t.CreatTree(_Infos, 256, invalid);
HuffManNode*root = t.GetRoot();
_GenrateHuffManCode(root);
}
void _GenrateHuffManCode(HuffManNode* root)
{
if (root == NULL)
{
return;
}
_GenrateHuffManCode(root->_left);
_GenrateHuffManCode(root->_right);
if ((root->_left == NULL) && (root->_right == NULL))
{
HuffManNode*cur = root;
HuffManNode*parent = cur->_parent;
string &code = _Infos[cur->_weight._ch]._code;
while (parent)//从叶子节点走到根结点
{
if (parent->_left == cur)
code += '0';
else
code += '1';
cur = parent;
parent = cur->_parent;
}
reverse(code.begin(), code.end());
}
}
//下面进行文件压缩
void CompressFile(const char *filename)
{
Compress(filename);
string compressFile = filename;
compressFile += ".huffman";
FILE *FinCompress = fopen(compressFile.c_str(), "wb");
assert(FinCompress);//对压缩文件的命名处理
GenerateHuffManCode();//产生编码
FILE *fout = fopen(filename, "rb");
assert(fout);
//进行文件压缩
unsigned char inch = 0;
int index = 0;
char ch = fgetc(fout);
while (ch!=EOF)
{
string&code = _Infos[(unsigned char)ch]._code;
for (int i = 0; i < code.size(); ++i)
{
++index;
inch <<= 1;
if (code[i] == '1')
{
inch |= 1;
}
if (index == 8)
{
fputc(inch, FinCompress);
index = 0;
inch = 0;
}
}
ch = fgetc(fout);
}
if (index != 0)
{
inch <<= (8 - index);
fputc(inch,FinCompress);
}
fclose(fout);
FileInfo invalid;
CreateConfig(filename,invalid);
}
void CreateConfig( const char* filename,FileInfo invalid)
{
string ConfigFile = filename;
ConfigFile += ".config";
FILE *FileConfig = fopen(ConfigFile.c_str(), "wb");
assert(FileConfig);
char ch[256];
string tempcount;
int i = 0;
tempcount= _itoa(COUNT, ch, 10);
while (i < tempcount.size())
{
fputc(tempcount[i],FileConfig);
i++;
}//将总的字符数写入配置文件
fputc('\n', FileConfig);
for (size_t i = 0; i < 256; i++)
{
if (_Infos[i] != invalid)
{
string chInfo;
chInfo.clear();
if (_Infos[i]._count>0)
{
chInfo += _Infos[i]._ch;
chInfo += ',';
char ch[256]; //转换成的字符可能足够长
_itoa(_Infos[i]._count,ch, 10);
chInfo += ch;
for (int j = 0; j < chInfo.size(); j++)
{
fputc(chInfo[j], FileConfig);
}
fputc('\n', FileConfig);
}
}
}
fclose(FileConfig);
}
};
void TestFileCompress()
{
FileCompress FC;
FC.CompressFile("fin.txt");
cout << "压缩成功" << endl;
}
Uncompress.h
# include
using namespace std;
# include"HuffMan.h"
# include"filecompress.h"
class Uncompress
{
private:
FileInfo _UNinfos[256];
LongType Count;
public:
Uncompress()//哈希表的初始化
{
for (int i = 0; i < 256; i++)
{
_UNinfos[i]._ch = i;
}
Count = 0;
}
bool _Uncompress(const char *Ufilename)//读配置文件
{
string Configfile = Ufilename;
Configfile += ".config";
FILE *fpConfig = fopen(Configfile.c_str(), "rb");
assert(fpConfig);
string line;
unsigned char ch = fgetc(fpConfig);
while (ch != '\n')
{
line += ch;
ch =fgetc(fpConfig);
}//读取第一个字符
Count = atoi(line.substr(0).c_str());//(总的字符个数)
ch = fgetc(fpConfig);//读入下一行字符
line.clear();
int j = 0;
while (!feof(fpConfig))
{
j++;
while (ch != '\n')
{
line += ch;
ch = fgetc(fpConfig);
}
if (line.empty())
{
line += '\n';
ch = fgetc(fpConfig);
while (ch != '\n')
{
line += ch;
ch = fgetc(fpConfig);
}
}
ch = fgetc(fpConfig);
unsigned char tempch = line[0];//将第一个字符转换成无符号数和下标对应起来
//尤其要注意这里稍微不注意就全乱码了
_UNinfos[tempch]._count = atoi(line.substr(2).c_str());//截取字符串并转换成整型数据
line.clear();
}
return true;
}
void GenrateHuffManCode(HuffManNode* root)//重构哈夫曼树
{
if (root == NULL)
{
return;
}
GenrateHuffManCode(root->_left);
GenrateHuffManCode(root->_right);
if ((root->_left == NULL) && (root->_right == NULL))
{
HuffManNode*cur = root;
HuffManNode*parent = cur->_parent;
string &code = _UNinfos[cur->_weight._ch]._code;
while (parent)//从叶子节点走到根结点
{
if (parent->_left == cur)
code += '0';
else
code += '1';
cur = parent;
parent = cur->_parent;
}
reverse(code.begin(), code.end());
}
}
bool UncomPress(const char *UNfilename)//文件的解压缩过程
{
_Uncompress(UNfilename);
HuffMan Re_huffTree;
FileInfo invalid;
HuffManNode*root = Re_huffTree.CreatTree(_UNinfos, 256, invalid);//重构哈夫曼树
GenrateHuffManCode(root);
//打开文件
string UnComPressFile = UNfilename;
UnComPressFile += ".Unhuffman";
FILE *UCPfile = fopen(UnComPressFile.c_str(), "wb");
string ComPressFile = UNfilename;
ComPressFile += ".huffman";
FILE *CPfile = fopen(ComPressFile.c_str(), "rb");
//解压缩字符写入文件
HuffManNode*tempRoot = root;//获得其根结点
while (!feof(CPfile))
{
unsigned char ch = fgetc(CPfile);
int bitCount = 7;
for (int i = bitCount; i >= 0; i--)
{
if (ch&(1 << i))
{
tempRoot = tempRoot->_right;
}
else
{
tempRoot = tempRoot->_left;
}
if (!tempRoot->_left&&!tempRoot->_right)//做到这里
{
fputc(tempRoot->_weight._ch, UCPfile);
Count--;
tempRoot = root;
}
if (Count <= 0)
{
break;
}
}
if (Count <= 0)
{
break;
}
}
return true;
}
};
void TestUNCP()
{
Uncompress Uncp;
Uncp.UncomPress("fin.txt");
}