目录
项目背景
一、技术需求背景
随着信息技术的快速发展,数据量激增,尤其在多媒体领域。文件压缩技术因此变得至关重要,它能有效减少存储空间需求,降低成本,并提高网络传输效率。此外,压缩文件还能增强数据安性,保护隐私,同时提高跨平台兼容性。对于云服务和移动设备而言,压缩技术优化了存储和带宽资源,降低了能耗,实现了绿色环保。总之,文件压缩技术在降低存储成本、提升传输效率和保障数据安全方面发挥着重要作用。
二、算法优势背景
Huffman 编码是一种高效的无损数据压缩算法,其核心优势在于根据字符出现频率动
态分配可变长度的编码,从而实现高压缩比。此算法特别适合字符频率分布不均的数
据集,能够显著减少文件大小,节省存储空间。由于其压缩和解压缩过程快速,
Huffman 编码在文件存储、多媒体传输等领域得到广泛应用。此外,它保证了压缩数
据的完全可逆性,满足了对数据完整性有严格要求的应用场景。
项目简介
一、基本原理
构建一个基于字符出现频率的最优二叉树——Huffman 树。在这个树中,出现频率高的字符被赋予较短的编码,而频率低的字符则被赋予较长的编码。通过这种方式,整体数据的编码长度得以最小化,实现压缩。编码过程中,从根节点到叶节点的路径表示字符的 Huffman 编码,左分支代表0,右分支代表 1。这种根据概率分配编码长度的方法,使得 Huffman 编码在数据压缩领域非常效。
二、具体步骤
1、统计原文件中字符频次: 打开文件并读取每个字符出现的次数。
//1.统计源文件中每个字符出现的次数
FILE* fIn = fopen(filepath.c_str(), "rb");//注意:二进制文件和文本文件,对于二进制文件要以二进制读取
if (nullptr == fIn)
{
cout << "打开文件失败" << endl;
return;
}
//打开文件成功,则开始读文件
uch 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]]._appearCount++;
}
}
2、 使用字符频次创建 Huffman 树: 使用优先队列( 小堆) 根据字符频次创建Huffman 编码。每次从堆中取两个最小的结点作为左右子树, 其中最小的是左子树。 再将两者的权值相加得到二者的父节点, 再重新将这个二叉树放进堆中。 直到森林中只有一个二叉树。
class HuffmanTree
{
typedef HuffmanTreeNode<W>Node;
class Compare
{
public:
bool operator()(const Node* x, const Node* y)const
{
return x->_weight > y->_weight;
}
};
public:
//默认构造一个空的二叉树
HuffmanTree()
:_root(nullptr)
{}
//也可以构造一个
HuffmanTree(const std::vector<W>& vw, const W& valid)//注意
{
//创建一个优先级队列
std::priority_queue<Node*, std::vector<Node*>, Compare>q;
for (auto& e : vw)
{
if (valid != e)//出现次数一定不是0
{
q.push(new Node(e));//用一个for循环将各权值所构成的结点放进优先级队列中
}
}
//当调试时在下面打断点,发现结点个数是256,为什么?
//因为出现0次时不需要往里放,同时huffman是泛型的,不一定出现ByteInfo,故不能用if(e.)如何解决:给构造函数增加一个参数:一个无效的权值
while (q.size() > 1)//当优先级队列中结点数超过1,取出两个较小值来构建新二叉树森林
{
Node* left = q.top();//因为该优先级队列是以小堆为主,则取出最小的放为左子树
q.pop();
Node* right = q.top();
q.pop();
//将left和rught作为新节点的左右子树后,创建新父节点为左右子树权值之和并放入优先级队列中
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();
}
~HuffmanTree()
{
Destroy(_root);
}
Node* Getroot()
{
return _root;
}
private:
Node* _root;
void Destroy(Node*& root)
{
if (root)
{
Destroy(root->_left);
Destroy(root->_right);
delete root;
root = nullptr;
}
}
};
3、 获取 Huffman 编码: 从创建好的 Huffman 树的根节点开始往叶子节点走, 往左走就是‘0’ ,往右走即为‘1’ , 直到走到叶子节点获得仅有 0 和 1 的序列, 再用逆置的方式获取每个字符的Huffman 编码。
while (true)
{
size_t rdSize = fread(rdBuff, 1, 1024, fIn);
if (0 == rdSize)
break;
for (size_t i = 0; i < rdSize; ++i)
{
string&strCode=_fileInfo[rdBuff[i]]._chCode;
for (size_t j = 0; j < strCode.size(); ++j)
{
bits <<= 1;
if ('1' == strCode[j])
bits |= 1;
bitsCount++;
if (8 == bitsCount)
{
fputc(bits, fout);
bits = 0;//清不清0无所谓
bitsCount = 0;
}
}
}
}
//不够8个比特位没有写进文件,退出while循环,处理方式:
if (bitsCount > 0 && bitsCount < 8)
{
bits <<= (8 - bitsCount);
fputc(bits, fout);
}
4、 文件压缩: 再次遍历文件,将每个字符替换为它的哈夫曼编码,并按字节写入压缩文件。如果编码不足一个字节, 先将其移到高位, 进行适当的位填充。同时,还需将压缩文件的格式(头部信息和数据信息) 写入压缩文件中,用于解压缩时重构哈夫曼树。
//压缩文件格式:
void filecompressHM::WriteHeadInfo(const string& filepath, FILE* fout)
{
//获取源文件的后缀
string headInfo;
headInfo += GetFilePostFix(filepath);
headInfo += '\n';
//2.构造频次信息
size_t appearLineCount = 0;
string chInfo;
for (auto& e : _fileInfo)
{
if (0 == e._appearCount)
continue;chInfo += e._ch;
chInfo += ':';
chInfo += std::to_string(e._appearCount);
chInfo += '\n';
appearLineCount++;
}
headInfo += std::to_string(appearLineCount);
headInfo += '\n';
fwrite(headInfo.c_str(), 1, headInfo.size(), fout);//压缩文件格式的头部信息
fwrite(chInfo.c_str(), 1, chInfo.size(), fout);//数据信息}
5、 文件解压缩: 读取压缩文件, 并获取压缩文件的格式, 根据其字符频率信息重构哈夫曼树。然后逐位读取压缩文件中的编码,根据哈夫曼树进行解码,将解码后的字符依次写入解压缩文件,直到压缩文件读取完毕。
三、项目优缺点
1、优点
Huffman 编码通过构建基于字符频率的最优二叉树实现压缩,特点是高压缩比、无损压缩、适应性强、压缩和解压缩效率高,适用于文本和数据压缩。
2、缺点
由于每次在压缩文件里放置了压缩文件头部信息以及每个字符出现次数, 当文件过大时, 将会对压缩率有一定的影响。而且解压缩时通过不断遍历还原的 huffman 树来进行解压,对解压的效率也会有一定的影响,因此在工程中一般很少使用 huffman 直接进行压缩测试工具和相关技术。
测试工具和相关技术
测试工具:Beyond Compare、 VS2019
测试相关技术:Huffman树、优先级队列、文件流提取和写入、位操作、哈夫曼编码
测试用例
测试计划
测试 | 后端开发 | 前端开发 | 提测日期 | 测试 | 测试日期 | 测试结果 |
压缩功能 | 佘娟妹 | 佘娟妹 | 10.25 | 佘娟妹 | 10.26 | 出现bug |
解压功能 | 佘娟妹 | 佘娟妹 | 10.25 | 佘娟妹 | 10.26 | 通过 |
界面测试 | 佘娟妹 | 佘娟妹 | 10.25 | 佘娟妹 | 10.26 | 通过 |
兼容性测试 | 佘娟妹 | 佘娟妹 | 10.25 | 佘娟妹 | 10.26 | 通过 |
易用性测试 | 佘娟妹 | 佘娟妹 | 10.25 | 佘娟妹 | 10.26 | 通过 |
安全性测试 | 佘娟妹 | 佘娟妹 | 10.25 | 佘娟妹 | 10.26 | 通过 |
执行测试过程
功能测试:覆盖退出、压缩、解压功能
界面测试:覆盖了菜单页、压缩界面和解压界面,发现1bug。
兼容性测试:覆盖了文件常见类型:jpg、png、word、txt、pdf等
安全性测试:覆盖了压缩、解压是否会丢失数据,发现1个bug。
bug描述
bug1:出现越界问题
问题原因:待压缩文件中存在中文字符,诱发程序崩溃
电脑系统:Win11 ,编译环境:VS2019
详细原因:由于待压缩文件中存在中文字符,而字符的数据类型是char为-128~127,而中文需要两个字符表示的。而这两个字符不在ASCII码表的范围内。只能使用无符号的char,使得代码不崩溃。
Bug2描述:
当文件为二进制文本时,压缩文件和解压缩文件不一致,数据丢失。
原因:在读取文件和写入文件时均以文本格式读取和写入。
对比工具:Beyond Compare
解决:读取文件和写入文件时均以二进制格式读取和写入。
测试结果
-
哈夫曼文件压缩算法在处理文本文件时具有较高的压缩比和较好的压缩效果,但对于图像和音频文件的压缩效果有限。
-
当对于内容特别大的文件时,会大大影响压缩效率,增加压缩和解缩时间。
-
哈夫曼文件压缩算法在特定的应用场景下具有一定的价值,但在实际使用中需要根据具体情况进行选择和优化。