最近学习了哈夫曼树,就想着实际应用下。于是便有了现在的文件压缩项目。
功能:可对任意格式的文件(.txt,.png,.doc,.pdf,等)进行压缩,解压缩(此程序压缩的文件)。
1.文件压缩简介
<1>什么是文件压缩
文件压缩是指在不丢失有用信息的前提下,缩减数据量以减少存储空间,提高其传输、存储和处理效率,或按照一定的算法对文件中数据进行重新组织,减少数据的冗余和存储的空间的一种技术方法。
<2>压缩的本质及好处
1.压缩的目的是让文件变小,减少文件所占的存储空间。
2. 紧缩数据存储容量,减少存储空间
3. 可以提高数据传输的速度,减少带宽占用量,提高通讯效率
2.哈夫曼树
<1>什么是哈夫曼树呢?
哈夫曼树是一种带权路径长度最短的二叉树,也称为最优二叉树。
带权路径长度: 从二叉树的根结点到二叉树中所有叶结点的路径长度与相应权值的乘积之和为该二叉树的带权路径长度WPL。
如下图:
上述三棵树的带权路径长度分别为:
(a)1 * 2 + 2 * 2 + 6 * 2 + 7 * 2 = 32
(b)7 * 3 + 2 * 3 + 6 * 2 + 1 = 40
(c)7 * 1 + 1 * 3 + 2 * 3 + 6 * 2 = 28
将带权路径最小的二叉树称为Huffman树。
<2> huffman树构建
- 由给定的n个权值{ w1, w2, w3, … , wn}构造n棵只有根节点的二叉树森林F={T1, T2 , T3, … ,Tn},每棵二叉树Ti只有一个带权值wi的根节点,左右孩子均为空。
- 重复以下步骤,直到F中只剩下一棵树为止
1)在F中选取两棵根节点权值最小的二叉树,作为左右子树构造一棵新的二叉树,新二叉树根节点的权值为其左右子树根节点的权值之和
2)在F中删除这两棵二叉树
3)把新的二叉树加入到F中
如下图:以1 6 2 7 为例,先排好序,
<3>haffman编码
3.文件的编写思路及流程
说了半天那哈夫曼树到低和文件压缩有什么关系呢?
我们可以想一下,正常的话文件中的一个字符要占用一个字节,那如果我们可以找到一个编码来替换他的话,是不是占用的空间就比较小的,也就是达到了文件压缩的目的,来梳理下大致思路。
<1>文件的压缩
1.统计文件中每个字符出现的次数
打开文件的时候我们最好判断一下文件是否为空的,空的话我们就不用进行压缩了,同时空的也有可能会引发些程序的问题。
2.用统计的字符信息来创建一个哈夫曼树
3.获取哈夫曼编码
4.用获取的编码对原文件进行改写(要注意当最后一个字节不满时,需要单独处理),生成一个新的压缩文件。
5.只是将字符替换成编码就够了吗?
我们可以想一下,当文件压缩完成后随之字符的相关信息也会失效,那么怎样解压呢,没有字符信息就没办法构建对应的哈夫曼树,也就没办法进行解压,故我们还应该写入原文件的相关信息。如文件的类型、每个种字符及出现的次数,当然这些信息最好写在文件的开头,方便我们进行读取。
<2>文件的解压缩
1.识别文件的类型能否解压。
2.读取信息(文件类型,每个种字符及出现的次数),生成对应解压缩文件名称。
3.还原哈夫曼树,进行解压缩(编码替换成对应字符)。
下面是主要代码,详细代码可以点击我的gitee链接查看
文件压缩项目
哈夫曼树的构建
template<class T>
struct HuffmainTreeNode
{
HuffmainTreeNode<T>* _left;
HuffmainTreeNode<T>* _right;
HuffmainTreeNode<T>* _parent;
T _weight;
HuffmainTreeNode(const T& weight = T())
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_weight(weight)
{}
};
template<class T>
class HuffmainTree
{
typedef HuffmainTreeNode<T> Node;
//仿函数
class compater
{
public:
bool operator()(const Node* left, const Node* right)
{
return left->_weight > right->_weight;
}
};
public:
HuffmainTree()
:_root(nullptr)
{}
//建小堆来存储数据
std::priority_queue<Node*, vector<Node*>, compater> q;
HuffmainTree(const vector<T> vw,const T& invaild)
{
for (auto& a : vw)
{
if(a != invaild)
q.push(new Node(a));
}
while (q.size() > 1)
{
Node* left = q.top();
q.pop();
Node* right = q.top();
q.pop();
Node* parent = new Node(left->_weight + right->_weight);
parent->_left = left;
left->_parent = parent;
parent->_right = right;
right->_parent = parent;
q.push(parent);
}
//根节点
_root = q.top();
}
~HuffmainTree()
{
Destory(_root);
}
Node* GetRootNode()
{
return _root;
}
private:
void Destory(Node* & _root)
{
if (nullptr == _root)
return;
Destory(_root->_left);
Destory(_root->_right);
delete _root;
_root = nullptr;
}
Node* _root;
};
压缩与解压缩
//构造函数
FileCoppressHM::FileCoppressHM()
{
_fileInfo.resize(256);
for (int i = 0; i < 256; i++)
{
_fileInfo[i]._ch = i;
}
}
//文件压缩
void FileCoppressHM::FileCompress(const string& FileCode)
{
//1.统计文件中每个字符出现的次数
FILE* fIn = fopen(FileCode.c_str(), "rb");
if (nullptr == fIn)
{
cout << "需要打开的文件不存在" << endl;
return;
}
//判断文件内容是否为空
char judge = fgetc(fIn);
if (EOF == judge)
{
cout << "该文件内容为空,无法解压。请更换文件。" << endl;
return;
}
else
{
//回起始位置
fseek(fIn, 0, SEEK_SET);
}
//统计文件字符次数
uchar rdBuff[1024];
while (true)
{
size_t rdSize = fread(rdBuff, 1, 1024, fIn);
if (0 == rdSize)
break;
for (size_t i = 0; i < rdSize; i++)
{
_fileInfo[rdBuff[i]]._chCount++;
}
}
//2.构建huffmian树
HuffmainTree<CharInfo> ht(_fileInfo, CharInfo());
//3.获取huffmian编码
auto root = ht.GetRootNode();
GenerrateHMCode(root);
//4.写入用来压缩的数据信息
string WriteFileName = GetFileName(FileCode) + ".zip";
FILE* fout = fopen(WriteFileName.c_str(), "wb");
WriteHeadInfo(FileCode, fout);
//5.用编码对文件进行改写
//文件指针恢复原点
fseek(fIn, 0, SEEK_SET);
size_t bitCount = 0;
char tempCh = 0;
while (true)
{
size_t rdSize = fread(rdBuff, 1, 1024, fIn);
if (0 == rdSize)
break;
for (size_t i = 0; i < rdSize; i++)
{
string& chCode = _fileInfo[rdBuff[i]]._chCode;
for (size_t j = 0; j < chCode.size(); j++)
{
tempCh <<= 1;
if ('1' == chCode[j])
tempCh |= 1;
bitCount++;
if (8 == bitCount)
{
fputc(tempCh, fout);
tempCh = 0;
bitCount = 0;
}
}
}
}
//最后一个字节有可能不足8位
if (bitCount > 1 && bitCount < 8)
{
tempCh <<= (8 - bitCount);
fputc(tempCh, fout);
}
fclose(fIn);
fclose(fout);
int a = 0;
}
//文件解压缩
void FileCoppressHM::UNFileCompress(const string& FileCode)
{
//1.识别文件
string suffix = GetFileSuffix(FileCode);
if (".zip" != suffix)
{
cout << "文件格式错误,请输入.zip文件" << endl;
return;
}
//2.读取解压缩需要的信息
FILE* fIn = fopen(FileCode.c_str(), "rb");
if (nullptr == fIn)
{
cout << "解压缩文件不存在" << endl;
return;
}
//生成解压缩文件名
string lineInfo;
GteLine(fIn, lineInfo);
string UNCompressName = GetFileName(FileCode) + "ucp" + lineInfo;
//获取行信息
lineInfo = "";
GteLine(fIn, lineInfo);
size_t lineCount = atoi(lineInfo.c_str());
for (size_t i = 0; i < lineCount; i++)
{
lineInfo = "";
GteLine(fIn, lineInfo);
//行特殊处理
if ("" == lineInfo)
{
lineInfo += '\n';
GteLine(fIn, lineInfo);
}
uchar ch = lineInfo[0];
_fileInfo[ch]._ch = ch;
_fileInfo[ch]._chCount = atoi(lineInfo.c_str() + 2);
}
//3.还原huffmain树
HuffmainTree<CharInfo> ht(_fileInfo, CharInfo());
//4.解压缩
FILE* fout = fopen(UNCompressName.c_str(), "wb");
uchar rdBuff[1024];
HuffmainTreeNode<CharInfo>* root = ht.GetRootNode();
size_t charSize = 0;
while (true)
{
size_t rdSize = fread(rdBuff, 1, 1024, fIn);
if (0 == rdSize)
break;
for (size_t i = 0; i < rdSize; i++)
{
char byte = rdBuff[i];
for (size_t j = 0; j < 8; j++)
{
if (byte & 0x80)
root = root->_right;
else
root = root->_left;
byte <<= 1;
if (nullptr == root->_left && nullptr == root->_right)
{
fputc(root->_weight._ch, fout);
root = ht.GetRootNode();
charSize += 1;
//等于根节点权值则文件压缩完毕
if (charSize == root->_weight._chCount)
break;
}
}
}
}
fclose(fIn);
fclose(fout);
}
//获取huffmain编码
void FileCoppressHM::GenerrateHMCode(HuffmainTreeNode<CharInfo>* root)
{
if (nullptr == root)
return;
GenerrateHMCode(root->_left);
GenerrateHMCode(root->_right);
if (nullptr == root->_left && nullptr == root->_right)
{
string& chCode = _fileInfo[root->_weight._ch]._chCode;
HuffmainTreeNode<CharInfo>* cur = root;
HuffmainTreeNode<CharInfo>* parent = cur->_parent;
while (parent)
{
if (cur == parent->_left)
chCode += '0';
else
chCode += '1';
cur = parent;
parent = cur->_parent;
}
reverse(chCode.begin(), chCode.end());
}
}
//写入解压缩的数据信息
void FileCoppressHM::WriteHeadInfo(const string& FileCode, FILE* fout)
{
string HeadInfo;
size_t LineCount = 0;
//文件后缀
HeadInfo += GetFileSuffix(FileCode);
HeadInfo += '\n';
//获取字符频次信息
string charInfo;
for (auto a : _fileInfo)
{
if (0 == a._chCount)
continue;
charInfo += a._ch;
charInfo += ':';
charInfo += std::to_string(a._chCount);
charInfo += '\n';
LineCount++;
}
HeadInfo += std::to_string(LineCount);
HeadInfo += '\n';
fwrite(HeadInfo.c_str(), 1, HeadInfo.size(), fout);
fwrite(charInfo.c_str(), 1, charInfo.size(), fout);
}
//获取文件后缀
string FileCoppressHM::GetFileSuffix(const string& FileCode)
{
size_t pos = FileCode.rfind('.');
string Suffix = FileCode.substr(pos);
return Suffix;
}
//获取获取文件名称
string FileCoppressHM::GetFileName(const string& FileCode)
{
size_t pos = FileCode.find('.');
string FileName = FileCode.substr(0, pos);
return FileName;
}
//获取一行
void FileCoppressHM::GteLine(FILE* fIn, string& lineInfo)
{
while (!feof(fIn))
{
char ch = getc(fIn);
if (ch == '\n')
break;
lineInfo += ch;
}
}