…当初上数据结构课程的时候写的程序,忘了发了。现在记不清一些细节上的东西了。决定就只把代码贴上来了,心得什么的已经没了,比如踩过的坑什么的…
代码里也有一些关键注释。
先上代码
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int ShowHelp(){
cout<<"please input the instructions,Z means zip the sourcefile,X means extract files,for example:'huftest Z a.txt a.huf'"<<endl;
return 0;
}
struct Node
{
// char massage;
long long int index; //用于存字符对应ASCII码
long long int weight;
long long int parent,lcd,rcd;
};
struct hufftree
{
Node *nodes;
long long int leafnum;
};
struct BitOStream
{
ostream *ostr; // 指向目标输出流实例
int bytebuffer; // 字节缓冲,准备写入流的位要先写到字节缓冲中,当字节缓冲填满8个bit后,才将一个字节写入目标流中
int bitmask; // 位写入屏蔽字,准备写入bytebuffer的对应位为1,其他位为0
};
//顺序查找避免sort的不稳定
int creathufftree(hufftree &T,int leafnum,long long int *nodeweight)
{
T.nodes=new Node[2*leafnum-1];
T.leafnum=leafnum;
for(int i=0;i<2*leafnum-1;i++) //初始化
{
T.nodes[i].index=i;
if(i<leafnum)
T.nodes[i].weight=nodeweight[i];
else T.nodes[i].weight=0;
T.nodes[i].parent=T.nodes[i].lcd=T.nodes[i].rcd=-1;
// T.nodes[i].massage=char(T.nodes[i].weight);
}
for(int i=0;i<leafnum-1;i++){
int k1,k2;
int t1,t2;
t1=t2=-1;
for(int j=0;j<leafnum+i;j++){
if(T.nodes[j].parent==-1)
{
if(t1==-1||T.nodes[j].weight<T.nodes[t1].weight){
t2=t1; //t2需要更新到t1的位置
t1=j;
}
else if(t2==-1||T.nodes[j].weight<T.nodes[t2].weight){
t2=j;
}
}
else{
continue; //parent!=-1则已经遍历过
}
}
k1=min(t1,t2);
k2=max(t1,t2);
// k1=t1;k2=t2;
//将挑选出来的两个节点作为左右子放进新节点T.nodes[leafnum+i]中
T.nodes[leafnum+i].weight=T.nodes[k1].weight+T.nodes[k2].weight;
T.nodes[leafnum+i].parent=-1;
T.nodes[leafnum+i].lcd=k1;
T.nodes[leafnum+i].rcd=k2;
T.nodes[k1].parent=leafnum+i;
T.nodes[k2].parent=leafnum+i;
}
return 0;
}
int Put_Bit(BitOStream &Byte_str, int bit){
if(bit) Byte_str.bytebuffer |= Byte_str.bitmask;//将1存入buffer中
Byte_str.bitmask>>=1; //屏蔽字右移
if(Byte_str.bitmask==0) //右移八位,已经凑满一字节,写入输出流
{
Byte_str.ostr->put(Byte_str.bytebuffer);
Byte_str.bitmask=0x80;
Byte_str.bytebuffer=0;
}
return 0;
}
int cal(int mask){
int i;
for(i=0;i<8;i++)
{
if(pow(2,i)==mask)
return i;
}
return 0;
}
int Finish_Put(BitOStream &Byte_str){
int res=0;
if(Byte_str.bitmask!=0x80) //还有剩余未满八位的编码
{
res=7-cal(Byte_str.bitmask); //最后一字节的有效编码位数,mask右移n位,就有n位有效
Byte_str.ostr->put(Byte_str.bytebuffer);
//重新初始化
Byte_str.bitmask=0x80;
Byte_str.bytebuffer=0;
}
return res;
}
//字符编码
vector<string> huffcode(hufftree &T){
vector<string> Zipcodes(T.leafnum); //256种字符
char *str=new char[2*T.leafnum]{}; //考虑树深度最大可能超过256
for(long long int i= 0 ; i < T.leafnum ; i++){
long long int k,s=2*T.leafnum-2; //由于从节点往根遍历,故逆序存入str中
for(long long int j=i,k=T.nodes[i].parent;k!=-1;j=k,k=T.nodes[k].parent)
str[s--]=(j==T.nodes[k].lcd ? '0' : '1');
//找到根后退出循环
Zipcodes[T.nodes[i].index]=str+s+1;
}
delete []str;
return Zipcodes;
}
int destroy_tree(hufftree &T){
delete []T.nodes;
T.nodes=NULL;
T.leafnum=0;
return 0;
}
int huffmanZIP(long long int *weights,ifstream &instr,ofstream &outstr)
{
hufftree T;
//构建哈夫曼树
creathufftree(T,256,weights);
//构建哈夫曼编码
vector<string> Zipcodes=huffcode(T);
//将树存入压缩文件(编码头)
int *codehead=new int[2*T.leafnum-1]; //2*leafnum-1(511)个节点
for(int i=0; i<2*T.leafnum-1; i++) //遍历每个节点
codehead[i]=T.nodes[i].parent; //i的值等于index的值
//保存父链
for(int i=0; i<2*T.leafnum-1; i++)
outstr.put(codehead[i]-255); //parent范围256-510大于1字节
outstr.put(0); //预留一个字节
//根据编码将源文件对应编码位流写入压缩文件
BitOStream Byte_str;
//初始化
Byte_str.bitmask=0x80;//十进制128,二进制10000000
Byte_str.ostr=&outstr;
Byte_str.bytebuffer=0;
//读取内容
char ch;
int rest;
while(instr.get(ch)){
string Acode=Zipcodes[(int)(unsigned char)(ch)];
for(char c:Acode){ //遍历string,将1写入buffer
Put_Bit(Byte_str,(c=='1'));
}
}
rest=Finish_Put(Byte_str);//最后一个字符遍历完后检查是否有不满8位的情况
outstr.seekp(511,ios::beg);
outstr.put(rest);
//完成压缩,返回主函数关闭文档流
destroy_tree(T);
delete []codehead;
return 0;
}
int creatcodetree(hufftree &codeT,int *codehufftree){
codeT.leafnum=256;
codeT.nodes=new Node[511];
for(int i=0;i<511;i++){ //初始化
codeT.nodes[i].index=i; //0-255 index就是字符ASCII转化的int值
codeT.nodes[i].parent=codehufftree[i];
codeT.nodes[i].lcd=codeT.nodes[i].rcd=-1;
}
for(int i=0;i<511;i++)
{
if(codeT.nodes[codehufftree[i]].lcd!=-1&&codeT.nodes[codehufftree[i]].rcd!=-1)
continue;
if(codeT.nodes[codehufftree[i]].lcd==-1) //先出现的是左子
codeT.nodes[codehufftree[i]].lcd=i;
else codeT.nodes[codehufftree[i]].rcd=i;
}
return 0;
}
int HuffmanExtract(ifstream &instr,ofstream &outstr){
int codehufftree[511];
for(int i=0;i<511;i++) //前511字节是编码头,需将其还原成原哈夫曼树
{
char ch;
instr.get(ch);
codehufftree[i]=(int)(unsigned char)ch+255;
}
//-1是根节点的parent,而-1经过unsigned char转换后是255,即根节点parent=255
codehufftree[510]=-1;
char ch;
instr.get(ch);
int rest=(int)(unsigned char)ch; //rest!=0则表示还有不满八位的有效编码
hufftree codeT;
int root=510; //根节点是下标510的节点
creatcodetree(codeT,codehufftree);
//读取二进制编码,根据编码遍历树得到对应字符,并写入输出流
//输入流获取单字节,转化为01序列
instr.get(ch);
int j=(int)(unsigned char)ch;
queue <int> code;
for(int i=0x80;i>0;i>>=1) //第一个字符预处理
code.push(j&i);
int k=root;
while(instr.get(ch)){
int flag=8;
j=(int)(unsigned char)ch;
for(int i=0x80;i>0;i>>=1) //一次8个编码入队
code.push(j&i);
while(flag--){
if(code.front())
k=codeT.nodes[k].rcd;
else k=codeT.nodes[k].lcd;
code.pop();
if(codeT.nodes[k].lcd==-1&&codeT.nodes[k].rcd==-1)
{
outstr.put(codeT.nodes[k].index);
k=root; //k重新指向根
}
}
}
if(rest==0) rest=8;
//还剩余rest有效编码未处理
while(rest--){
if(code.front())
k=codeT.nodes[k].rcd;
else
k=codeT.nodes[k].lcd;
code.pop();
if(codeT.nodes[k].lcd==-1&&codeT.nodes[k].rcd==-1)
{
outstr.put(codeT.nodes[k].index);
k=root; //k重新指向根
}
}
return 0;
}
int main(int argc, char *argv[])
{
long long int weights[256]={0};
if(argc!=4)
ShowHelp();
else{
if (stricmp(argv[1],"Z")==0)
{
cout << "ZIP files..." << endl;
// 打开输入文件流
ifstream instr(argv[2], ios::in | ios::binary);//ios::binary--不对\n进行转码
if (!instr)
{
cerr << "Open " << argv[2] << " failure." << endl;
return 0;
}
// 从输入文件流逐字节读入,统计频率
char ch;
ofstream outstr(argv[3], ios::out | ios::binary);
// while(ch=instr.get()&&instr.eof())//eof()到达文件末返回值是流,instr(ch)读到文件未返回值为int型
while (instr.get(ch))
{
weights[(int)(unsigned char)ch]++;
}
instr.clear(); //读到了EOF,clear清除
instr.seekg(0,ios::beg); //指针重定位到0
huffmanZIP(weights,instr,outstr);
//调用函数压缩
//关闭文档流
outstr.close();
instr.close();
}
if(stricmp(argv[1],"X")==0)
{
cout<<"extract files..."<<endl;
//调用函数解压
ifstream instr(argv[2], ios::in | ios::binary);
ofstream outstr(argv[3],ios::out | ios::binary);
HuffmanExtract(instr,outstr);
instr.close();
outstr.close();
}
}
return 0;
}
具体思路就是利用哈夫曼算法,将文件以字节为单位,统计频率后重新编码,并将编码头写入压缩文件中,以达到压缩文件的目的,解压文件时先取出编码头中的哈夫曼树,再逐位取出压缩文件中二进制数,通过哈夫曼树解压出对应字符,写入解压文件中,以此实现解压操作。
测试情况:
(一) 程序概况:
大小:269 KB (275,816 字节);
占用空间:272 KB (278,528 字节);
(二) 压缩阈值:
512字节;(当需要压缩的文件大小小于512字节时压缩后的文件大小会大于源文件,某些情况下压缩后的文件大小可能会大于源文件,文件越小,压缩效果越差)
(三) 文件压缩情况:
(四) 问题分析:
程序压缩以及解压大型文件时耗时较久,并且,由于存有512字节的编码信息,小型文件在压缩后的文件大小极有可能超过源文件,压缩、解压过程中CPU占用率较高,约20%,操作大型文件内存占用较大。
程序使用说明:
使用方法: