文件压缩:VS2013采用C/C++
/******************** HuffmanTree.h *********************/
#pragma once
#include <iostream>
using namespace std;
#include <vector>
#include <queue>
template <class W>
struct HuffmanTreeNode//构造哈夫曼树的节点 三叉链
{
W _w;//数据值
HuffmanTreeNode<W>* _left;//左孩子
HuffmanTreeNode<W>* _right;//右孩子
HuffmanTreeNode<W>* _parent;//父亲
HuffmanTreeNode(const W& w)
:_left(NULL)
, _right(NULL)
, _w(w) //权值 次数
, _parent(NULL)
{}
};
template <class W>
class HuffmanTree //构造哈夫曼树
{
typedef HuffmanTreeNode<W> Node;
struct PtrNodeCompare // 迭代器 比较方法-->权值比较
{
bool operator()(Node* left, Node* right)//重载()
{
return left->_w > right->_w;
}
};
public:
HuffmanTree()
:_root(NULL)
{}
HuffmanTree(W* w, size_t n,const W& invalid)//invalid用来阻止没必要构建的字符(即为空)
{
//优先级队列构造小堆,每次拿出最小元素去构建HuffmanTree节点
priority_queue <Node*, vector<Node*>, PtrNodeCompare> minheap;
for (size_t i = 0; i < n; ++i)
{
if (w[i] != invalid)
//两个模板类型(不确定类型)比较,采用W._count比较,类中已经重载过 !=
{
minheap.push(new Node(w[i]));
}
}//小堆构建完成
if (minheap.size() == 0)
{
cout << "无效文件" << endl;
exit(0);
}else if (minheap.size() == 1)
{
Node* right = minheap.top();
_root = new Node(right->_w);
right->_parent = _root;
_root->_right = right;
}
else
{
while (minheap.size() > 1)//每次拿到最小的元素后调整堆
{
Node* left = minheap.top();
minheap.pop();
Node* right = minheap.top();//huffman模型 right比left大
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();
}
}//Huffman树构造完成后,所有的元素均位于叶子节点
Node* GetRoot()
{
return _root;
}
~HuffmanTree() //递归销毁不能直接在类内直接操作(类内销毁,死循环) 类外定义递归函数
{
Destory(_root);
_root = NULL;
}
void Destory(Node* root) // 递归销毁 时间复杂度O(N)
{
if (root == NULL)
{
return;
}
Destory(root->_left);
Destory(root->_right);
delete root;
}
private://压缩文件用不到此操作 防止类外被调用 定义为私有
HuffmanTree(const HuffmanTree<W>& t); // 防止拷贝构造
HuffmanTree<W>& operator= (const HuffmanTree<W>& t); //防拷贝 只声明 不定义 防止系统合成
protected:
Node* _root; //哈夫曼树节点
};
void TestHuffmanTree()//测试是否构成Huffman树
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
HuffmanTree<int> t (a, sizeof(a) / sizeof(a[0]),0);
}
/*********************** FileCompress.h ***********************/
#pragma once
//#include <cassert>
#include <string>
#include <stdio.h>
#include <windows.h>
#include <fstream>
#include <algorithm>
#include <assert.h>
#pragma warning (disable: 4996)
using namespace std;
#include "HuffmanTree.h"
typedef long long LongType;
//compress 压缩
struct CharInfo
{
char _ASCII; //字符
LongType _count; //次数
string _code; //权值
CharInfo operator+(const CharInfo& info)
{
CharInfo ret;
ret._count = _count + info._count;
return ret;
}
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;
}
bool operator==(const CharInfo& info)
{
return _count == info._count;
}
};
class FileCompress
{
typedef HuffmanTreeNode<CharInfo> Node;
struct TmpInfo
{
char _ch; //字符
LongType _num;//数量
};
public:
FileCompress()
{
for (size_t i = 0; i < 256; ++i)
{
_infos[i]._ASCII = i;
_infos[i]._count = 0;
}
}
void Compress(const char* file)//压缩
{
//1.统计字符出现的次数
FILE* f_out = fopen(file, "r");
assert(f_out);
//fgetc 从文件指针stream指向的文件中读取一个字符
//读取一个字节后,光标位置后移一个字节
char character = fgetc(f_out);//ASCII码
while (character != EOF)
{
_infos[(unsigned char)character]._count++; //ASCII码对应的编号用作下标
character = fgetc(f_out);
}
//2.构建huffman tree
CharInfo invalid;
invalid._count = 0;
//如果invalid._count = 0,说明次数是0,没有出现
HuffmanTree<CharInfo> tree(_infos, 256, invalid);
//3.生成code
//直接放到_infos
GenerateHuffmanCode(tree.GetRoot());
/***************************************************************************/
//测试 是否生成Huffman编码 小堆创建失败(变大堆) 会造成越压越大
/***************************************************************************/
//4.压缩
string compressFile = file; //file->传进来的文件(名)
compressFile += ".huffman"; //string底层支持+=
//
FILE* f_in = fopen(compressFile.c_str(), "wb");//wb 二进制写
//compressFile.c_str() 相当于 "file".huffman
for (size_t i = 0; i < 256; ++i)
{
if (_infos[i]._count > 0)//tmpinfo只保存原文件含有的字符
{
TmpInfo tmpinfo;
tmpinfo._ch = _infos[i]._ASCII; //字符
tmpinfo._num = _infos[i]._count;//次数
//fwrite 二进制输出
//fwrite 参数1 数据块 2 大小 3 写多少次块 4 去向(文件)
fwrite(&tmpinfo, sizeof(TmpInfo), 1, f_in);//存备份 例如a4b4c3d2e2f1g1
}
}
/************************************************************/
//下面 是 结尾标志
/***********************************************************/
TmpInfo tmpinfo;
tmpinfo._num = -1; //_num初始化为-1 因为不可能有一个字符出现了-1次
fwrite(&tmpinfo, sizeof(TmpInfo), 1, f_in);
//文件已经读到尾部啦 重置文件指针
rewind(f_out);//rewind函数作用等同于 (void)fseek(stream,0L,SEEK_SET)
//fseek fseek函数是设置文件指针stream的位置
//参数1 文件指针 2 偏移位置(负数 反方向偏移)
//3 从哪儿开始偏移(SEEK_SET文件开头 SEEK_CUR当前位置 SEEK_END 文件结尾)
//ifs._Seekbeg;
character = fgetc(f_out); // 操作读取原始文件
//ifs.seekg(0);
size_t CeffectiveBit = 0;
char value = 0;
int pos = 0;
while (character != EOF)
{
// 采用引用效率高,不生成临时变量
string& code = _infos[(unsigned char)character]._code;//code是每个ch的huffman编码
for (size_t i = 0; i < code.size(); ++i)
{
if (code[i] == '0')//Huffman编码的第i位为0的写入方法
{
value &= ~(1 << pos);
}
else if (code[i] == '1')
{
value |= (1 << pos);
}
else
{
assert(false);
}
++pos;
if (pos == 8)
{
fputc(value, f_in);//8位Huffman编码生成一个value 存入文件 因此实现压缩
value = 0;
pos = 0;
}
CeffectiveBit++;
}
character = fgetc(f_out); //接着读数据
}
cout << "文件压缩有效位数 = " << CeffectiveBit << endl;
//不满8 剩余位以0补齐
if (pos > 0)
{
fputc(value, f_in);
}
fclose(f_out);
fclose(f_in);
}
/******************************************************************/
//完成后 huffman里边应该存储的信息为a4b4c3d2e2f1g1?-1 而后再跟编码
//
//存储举例:
//(低位)0000 1010 | 1011 1111 | 110 (补位5个0)
// 计算机存值 0 5 (十进制 80)| D F() |3 0 () 即 50 FD 03
/******************************************************************/
void GenerateHuffmanCode(Node* root)
{
if (root == NULL)
return;
//递归往上寻找 叶子开始 叶子位于父节点的左+0
if (root->_left == NULL && root->_right == NULL) //叶子节点 出口
{
string code; //存放huffman编码
Node* cur = root;
Node* parent = cur->_parent;
while (parent) // 左'0' 右'1'
{
if (cur == parent->_left)
{
code.push_back('0');
}
else
{
code.push_back('1');
}
//处理完当前节点,向上返完
cur = parent;
parent = cur->_parent;
}
//因为从下往上数,编码是反的,需要翻转
reverse(code.begin(), code.end());
//用unsigned char 是为了写入汉字
_infos[(unsigned char)root->_w._ASCII]._code = code;
//root->._w._ASCII root指向的节点是Node类型是CharInfo,_ASCII(字符值)是CharInfo里边定义的
return;
}
GenerateHuffmanCode(root->_left);
GenerateHuffmanCode(root->_right);
}
/************************** 解压缩 *******************************/
void Uncompress(const char* file)
{
size_t n = 0;
assert(file);
string uncompressFile = file;
size_t pos = uncompressFile.rfind('.'); //倒序查找'.' 返回'.'的位置(从前数)
//第一个参数是内容 第二个参数是从位置 可缺省 默认最后
assert(pos != string::npos); //没有找到返回npos(最大的无符号整形) 避免二义性
uncompressFile.erase(pos, uncompressFile.size() - pos);
//第一个参数 从哪个位置开始删 二 大小 (删多少) 缺省 删到结尾
uncompressFile += ".unhuffman";//测试所用 分辨与源文件的区别
FILE* fout = fopen(file, "rb");
assert(fout);
FILE* fin = fopen(uncompressFile.c_str(), "w");
assert(fin);
//读取压缩文件中的数据,解压缩
TmpInfo info;
fread(&info, sizeof(TmpInfo), 1, fout);
while (info._num != -1)
{
_infos[(unsigned char)info._ch]._ASCII = info._ch;
_infos[(unsigned char)info._ch]._count = info._num;
fread(&info, sizeof(TmpInfo), 1, fout);
//每读取一定的数据块 文件指针后移
}
//1.重建huffman tree
CharInfo invalid;
invalid._count = 0;
HuffmanTree<CharInfo> tree(_infos, 256, invalid);
Node* root = tree.GetRoot();
Node* cur = root;
LongType fileSize = root->_w._count;//跟等于所有叶子之和
cout << fileSize << endl;
//2.解压缩
char value = fgetc(fout);
while (1)
{
for (size_t pos = 0; pos < 8; ++pos)
{
// 判断value是'1' 还是'0'
if (value & (1 << pos)) //1与任何数等于任何数
cur = cur->_right;
else
cur = cur->_left;
++n;
if (NULL == cur->_left && NULL == cur->_right)
{
fputc(cur->_w._ASCII, fin); //还原完一个回到根节点,继续
cur = root;
if (--fileSize == 0)//8位没用完 文件已经读完
{
break;
}
}
}
if (fileSize == 0)//
{
break;
}
value = fgetc(fout);
}
cout << n << endl;
fclose(fout);
fclose(fin);
}
protected:
CharInfo _infos[256];
};//类结束
void TestFilecompress()
{
FileCompress fc;
size_t start = GetTickCount();
fc.Compress("aa.txt");
size_t end = GetTickCount();
cout << "UseTime:" << end - start << endl;
fc.Uncompress("aa.txt.huffman");
}
/****************** test.cpp *************/
#include "FileCompress.h"
int main()
{
TestFilecompress();
//TestHuffmanTree();
system("pause");
return 0;
}