过程
- 统计文件中字符出现的次数,利用数据结构中的堆建造Huffman树,字符出现次数多的编码短,出现次数少的编码长;
- 根据建造好的Huffman树形成编码,以对文件进行压缩;
- 将文件中出现的字符以及他们出现的次数写入配置文件,以便后续的解压缩;
- 根据配置文件读取相关信息,重建Huffman树,对压缩后的文件进行译码。
首先观察解压的文件和原文件是否相同,再通过Beyond Compare 4软件进行对比,验证程序的正确性。
可以发现一个规律:权值越小的,它的哈夫曼编码越长,权值越大的,哈夫曼编码越短
文件压缩的过程:
首先要统计待压缩文件中各字符出现的次数,然后构造哈弗曼编码,把编码写入压缩文件,不够一个字节的就在后面补零,因为要解压缩,所以还得写一个配置文件,配置文件里面写每个字符出现的次数。
具体过程:
a.读取文件,将每个字符,该字符出现的次数和权值构成哈夫曼树;
b.哈夫曼树是利用小堆构成,字符出现次数少的节点指针存在堆顶,出现次数多的在堆底;
c.每次取堆顶的两个数,再将两个数相加进堆,直到堆被取完,这时哈夫曼树也建成;
d.从哈夫曼树中获取哈夫曼编码,然后再根据整个字符数组,来获取出现了的字符的编码;
e.获取编码后每次凑满8位就将编码串写入到压缩文件;
f.写好配置文件,统计每个字符及其出现次数,保存到配置文件中。
文件解压的过程:
先去读配置文件,构建哈弗曼树和哈弗曼编码,用压缩文件里的编码去哈弗曼树里面找,找到叶子结点,就把叶子节点的字符写入解压缩文件中。
具体过程:
a.读取配置文件,统计所有字符的个数;
b.构建哈夫曼树,读解压缩文件,将所读到的编码字符的这个节点所含的字符,写入到解压缩文件中,直到将压缩文件读完;
c.解压缩完成之后,利用Beyond Compare 4软件,进行文件的测试。
项目测试:
通过Beyond Compare 4软件,对文件压缩前和压缩后的内容进行对比,验证程序的正确性。 性能测试,在release版本下会更高效一些,时间会缩短很多,因为 Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序,也就是博主编程用的版本;Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
源代码:
FileCompress.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdlib.h>
#include"HuffmanTree.h"
#include<algorithm>
#include<string>
#include<stdio.h>
#define __DEBUG__
struct CharInfo
{
char _ch;//出现的字符
long long _count;//字符出现的次数
string _code; //huffuman code
CharInfo(long long count = 0)
:_count(count)
{}
CharInfo operator +(const CharInfo& info)
{
CharInfo ret;
ret = _count + info._count;
return ret;
}
//比较的是次数 次数不等于0则入堆
bool operator != (const CharInfo& info)
{
return _count != info._count;
}
bool operator >(const CharInfo& info)
{
return _count > info._count;
}
bool operator <(const CharInfo& info)
{
return _count < info._count;
}
};
class FileCpmoress
{
typedef HuffumanTreeNode<CharInfo> Node;
public:
FileCpmoress()
{
//依次初始化
for (size_t i = 0; i < 256; ++i)
{
//汉字的编码是两个字节 char是0-127
//会发生数组越界,解决方法是将char强转为unsigned char,可表示范围为0~255。
_infos[(unsigned char)i]._ch = i;//i既是下标也是Ascall码
_infos[(unsigned char)i]._count = 0;//出现的次数
}
}
//内部类
struct Config
{
char _ch;
long long _count;
};
//压缩
//input.txt->input.txt.huffuman
void Compress(const char* file)
{
assert(file);
//1,统计字符出现的次数
//文本方式打开,会对‘\n’进行特殊处理,
//那如果这个字符本身就是'\n'.这就会出现问题,
//所以使用二进制打开,特点:不进行任何处理,是什么就是什么。
FILE* fout = fopen(file, "rb");
assert(fout);
char ch = fgetc(fout);
//feof是C语言标准库函数,其原型在stdio.h中,
//其功能是检测流上的文件结束符,如果文件结束,则返回非0值,
//否则返回0,文件结束符只能被clear()清除。
while (!feof(fout))
{
_infos[(unsigned char)ch]._count++;
ch = fgetc(fout);
}
//用charInfo构建的树里面既有权值又有次数(为了得到huffuman编码)
//出现次数大于0的才比较大小,所以需要重载次数比较的运算符
//2,构建huffuman tree
CharInfo invalid;//非法值
invalid._count = 0;//出现次数0次的为非法值
HuffumanTree<CharInfo> tree(_infos, 256, invalid);
//3.生成huffuman code(叶子节点)
string code;
_GetHuffumanCode(tree.GetRoot(), code);
//4,压缩(8位)
//fseek设置文件指针的位置,相对于当前位置
string compressFile = file;
compressFile += ".huffuman";
FILE* fin = fopen(compressFile.c_str(), "wb");
assert(fin);
////配置---解压缩是重建huffuman tree
Config info;
for (size_t i = 0; i < 256; ++i)
{
if (_infos[i]._count > 0)
{
info._ch = _infos[i]._ch;
info._count = _infos[i]._count;
fwrite(&info, sizeof(Config), 1, fin);
}
}
////分隔 多写8个字节
info._count = 0;
fwrite(&info, sizeof(Config), 1, fin);
//文件指针 偏移量 基准位置
char value = 0;//8个位
long long count = 0;
fseek(fout, 0, SEEK_SET);
ch = fgetc(fout);
/*EOF的16进制为0xFF(十进制为 - 1),特用在文本文件中,
因为在文本文件中数据是以ASCⅡ码值的形式存放,普通字符的ASCⅡ码的范围是32到127(十进制)
与EOF不冲突,因此可以直接使用;但是在二进制文件中,数据有可能出现 - 1
因此不能用EOF来作为二进制文件的结束标志,可以通过feof函数来判断。
也就是说feof这个函数可用于判断文件是否结束(包括文本文件和二进制文件)。*/
while (!feof(fout))//
{
string& code = _infos[(unsigned char)ch]._code;
//用位运算 左移是往高位移与方向无关
for (size_t i = 0; i < code.size(); ++i)
{
value <<= 1;
if (code[i] == '0')
{
value |= 0;
}
else if (code[i] == '1')
{
value |= 1;
}
++count;
if (count == 8)
{
fputc(value, fin);
value = 0;
count = 0;
}
}
ch = fgetc(fout);
}
if (count != 0)
{
value <<= (8 - count);
fputc(value, fin);
}
fclose(fin);
fclose(fout);
}
//解压缩
//input.txt.huffuman.txt->input.txt
void Uncompress(const char* file)
{
//1.读取配置文件信息--字符出现的次数
assert(file);
string uncompressFile = file;
size_t position = uncompressFile.rfind(".");//倒着找到.的位置
assert(position != string::npos);//不存在
uncompressFile = uncompressFile.substr(0, position);
uncompressFile += ".unhuffuman";
FILE* fout = fopen(file, "rb");//可以考虑用智能指针
assert(fout);
FILE* fin = fopen(uncompressFile.c_str(), "wb");
assert(fin);
Config info;
while (1)
{
fread(&info, sizeof(Config), 1, fout);
if (info._count == 0)
{
break;
}
else
{
_infos[(unsigned char)info._ch]._ch = info._ch;
_infos[(unsigned char)info._ch]._count = info._count;
}
}
//重建huffuman tree
CharInfo invalid;
invalid._count = 0;
HuffumanTree<CharInfo> tree(_infos, 256, invalid);
Node* root = tree.GetRoot();
long long count = root->_w._count;
//哈夫曼树的头结点的权值内的count存的是原来文件的字符个数
//解压缩
char ch = fgetc(fout);
Node* cur = root;
while (!feof(fout))
{
for (int pos = 7; pos >= 0; --pos)
{
if (ch &(1 << pos))
{
cur = cur->_right;
}
else
{
cur = cur->_left;
}
if (cur->_left == NULL && cur->_right == NULL)
{
fputc(cur->_w._ch, fin);
cur = root;
--count;
if (count == 0)
{
break;
}
}
}
ch = fgetc(fout);
}
uncompressFile.erase(position, uncompressFile.size() - position + 1);//删除.之后的字符串
fclose(fin);
fclose(fout);
}
protected:
//void _GetHuffumanCode(Node* root)
//{
// if (root == NULL)
// return;
// //叶子节点
// if (root->_left == NULL && root->_right == NULL)
// {
// //root是叶子节点,节点的权值_w是CharInfo类型,这个字符就是CharInfo中的ch
// //直接用引用,减少系统开销
// string& code = _infos[root->_w._ch]._code;
// Node* cur = root;//叶子节点
// Node* parent = cur->_parent;
// //循环到parent为NULL就结束
// while (parent)
// {
// if (cur == parent->_left)
// {
// code.push_back('0');
// }
// else if (cur == parent->_right)
// {
// code.push_back('1');
// }
// cur = parent;
// parent = cur->_parent;
// }
// //此时为逆置的编码
// reverse(code.begin(), code.end());
// return;
// }
// _GetHuffumanCode(root->_left);
// _GetHuffumanCode(root->_right);
//}
bool ReadLine(FILE *fOut, string& line)
{
char ch = fgetc(fOut);
if (feof(fOut))
return false;
while (!feof(fOut) && ch != '\n')
{
line += ch;
ch = fgetc(fOut);
}
return true;
}
void _GetHuffumanCode(Node* root, string& code)
{
//遍历
if (root == NULL)
return;
if (root->_left == NULL && root->_right == NULL)
{
_infos[(unsigned char)root->_w._ch]._code = code;
return;
}
_GetHuffumanCode(root->_left, code + '0');
_GetHuffumanCode(root->_right, code + '1');
}
private:
CharInfo _infos[256];//根据Ascall码统计字符出现的次数
};
void TestFileCompess()
{
/*FileCpmoress fc;
fc.Compress("ct.png");*/
FileCpmoress f1;
f1.Uncompress("ct.png.huffuman");
}
HuffmanTree.h
#pragma once
#include"Heap.h"
#include<functional>
template<class W>//权值
struct HuffumanTreeNode
{
HuffumanTreeNode<W>* _left;
HuffumanTreeNode<W>* _right;
HuffumanTreeNode<W>* _parent;
W _w;//权值
//W不一定是整型,可能是自定义类型,所以要给引用
HuffumanTreeNode(const W& w)
:_w(w)
, _left(NULL)
, _right(NULL)
, _parent(NULL)//构造三叉连
{}
};
template<class W>
class HuffumanTree
{
public:
typedef HuffumanTreeNode<W> Node;
HuffumanTree()
:_root(NULL)
{}
HuffumanTree(W* a, size_t n, const W& invalid)//根据权值构建树
{
//贪心算法选最小的(建小堆)//也可以使用库里面的优先级队列
struct NodeCompare
{
bool operator()(Node* l, Node* r)
{
return l->_w < r->_w;
}
};
Heap<Node*, NodeCompare> minHeap;
for (size_t i = 0; i < n; ++i)
{
if (a[i] != invalid)
minHeap.Push(new Node(a[i]));//将数组的元素都入堆并建小堆
}
//当size >1 时,堆中的元素都要选出最小的两个相加后再入堆
while (minHeap.Size() > 1)
{
Node* left = minHeap.Top();
minHeap.Pop();
Node* right = minHeap.Top();
minHeap.Pop();
Node* parent = new Node(left->_w + right->_w);
parent->_left = left;
parent->_right = right;
left->_parent = parent;
right->_parent = parent;
minHeap.Push(parent);//相加的结果要再放回去
}
_root = minHeap.Top();
}
//释放节点
~HuffumanTree()
{
Destroy(_root);
_root = NULL;
}
//二叉树的释放
void Destroy(Node* root)
{
if (root == NULL)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
}
Node* GetRoot()
{
return _root;
}
protected:
HuffumanTree(const HuffumanTree<W>&);//防拷贝
HuffumanTree<W>& operator=(const HuffumanTree<W>&);
Node* _root;
};
void TestHuffumanTree()
{
int a[] = { 1, 2, 3, 4 };
HuffumanTree<int> t(a, sizeof(a) / sizeof(a[0]), 0);
}
Heap.h
#pragma once
#include<vector>
template<class T>
struct Greater
{
bool operator()(const T& a, const T& b)
{
return a > b;
}
};
template<class T>
struct Less
{
bool operator()(const T& s, const T& b)
{
return a < b;
}
};
template<class T, class Compare>
class Heap
{
public:
Heap()
{}
Heap(T* a, int n)
{
_a.reserve(n);//增容(可以直接拷贝数据)
for (int i = 0; i < n; i++)
{
_a.push_back(a[i]);
}
//调整成堆
for (int j = (_a.size() - 2) / 2; j >= 0; --j)
{
//向下调整
_AjustDown(j);
}
}
void Push(const T& x)
{
_a.push_back(x);
_AjustUp(_a.size() - 1);
}
void Pop()
{
assert(!_a.empty());
swap(_a[0], _a[_a.size() - 1]);
_a.pop_back();
_AjustDown(0);
}
T& Top()
{
assert(!_a.empty());
return _a[0];
}
size_t Size()
{
return _a.size();
}
bool Empty()
{
return _a.empty();
}
protected:
//push_heap算法:向上调整
void _AjustUp(int child)
{
assert(!_a.empty());
int parent = (child - 1) >> 1;
while (child>0)
{
//如果孩子节点的值大于父节点的值
Compare com;
if (com(_a[child], _a[parent]))
{
swap(_a[child], _a[parent]);
child = parent;
parent = (child - 1) >> 1;
}
else
{
break;
}
}
}
protected:
//pop_heap:向下调整算法
void _AjustDown(int root)
{
int parent = root;
int child = parent * 2 + 1;
while (child < _a.size())
{
Compare com;
if ((child + 1 < _a.size()) && (_a[child + 1] > _a[child]))
{
++child;
}
if (com(_a[child], _a[parent]))
{
swap(_a[child], _a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
private:
vector<T> _a;
};
//void Test()
//{
// int a[] = { 1, 6, 8, 2, 3, 5, 7, 4, 9 };
// int len = sizeof(a) / sizeof(a[0]);
// Heap<int, Greater<int>> h(a, len);
// h.Top();
// h.Push(25);
// h.Pop();
// while (!h.Empty())
// {
// cout << h.Top() << " ";
// h.Pop();
// }
// cout << endl;
//}
Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;
#include"HuffmanTree.h"
#include"FileCompress.h"
int main()
{
//TestHuffumanTree();
TestFileCompess();
//Test();
system("pause");
return 0;
}