这个项目是大一写的,现在大三了,将它重新实现了一下,比原来的看起来更干净一点。
新的链接在这里手把手教你C语言哈夫曼压缩/解压缩
下面的都是以前写的,比较笨拙,但保留了原始的思路。
如果你想了解思路,可以看下面讲的细节(屎山)。
如果你水平比较高,不想看废话,可以看新实现的那篇。
现在已经可以对 1G任意文件(因为我只试了1G,再大的话有点费时间,足以说明代码是没大问题的)压缩后100%解压还原,文本压缩后约占原来70%~76% ,代码改动挺多的,下方的思路依然是正确的,只是代码有些bug(下方的代码只能100MB左右无误),
至此我要结束我的课设了,下面的代码也不太想改动了,这里放个文件提取码:qw77里面有三个版本:
1.调试版(这是给想了解过程的人看的),因为这个代码想用IDE调试简直是做梦,这出错率至少千万分之一,调不出来的,我都是拿宏调试+合理猜测推断,真的很不容易(从最初的150字节无误到现在的至少1G无误,鬼知道我经历了什么)。
2.无界面版,这个和调试版代码一样,只是不带宏,更整洁一些,DevCpp编译可通过。
3.带界面版,这个是带点花样的,用了基于对话框的win窗体程序(CodeBlocks的,不过好像复制到VS2019项目也能跑),还有音效、个性图标,给截个图看下
修改部分的代码,有的是bug(循环的判断与边界情况、数组分配过小越界),还有的是为了效率提升(主要是频繁打开文件的)。
能对任意格式的文件进行压缩
1.哈夫曼树的编码
2.C语言文件操作、按位运算(压榨存储空间的关键)
IDE:CodeBlocks
===========================================================
关键点
模块一:
计算机以字节为最小单位进行数据存储、一个字节8bit位,要做到对任意格式的压缩,映射是关键,也就是把字节数据映射成一个值(0b00000000~0b11111111),然后对它编码。
模块二:
要明确编码对象:字节值(0~0xFF),权值:字节值频次
模块三:
经实践,决定此处将编码表与压缩文件分别存放。
模块四:解压缩。
流程
模块 | 任务 | 结果 |
---|---|---|
模块一 | 统计文件中字节值频次,字节值作为编码对象,频次作为哈夫曼编码的权值 | 得到权值 |
模块二 | 利用权值对字节值进行哈夫曼编码 | 得到编码表 |
模块三 | 利用编码表改写文件实现压缩 | 得到压缩文件 |
模块四 | 利用编码表解压缩 | 实现解压缩 |
本人评估 | ||
模块 | 难度 | |
– | – | |
一 | ★ | |
二 | ★★ | |
三 | ★★★★★ | |
四 | ★★★ |
(讲解部分有详细注释)
//#define TEST_ShowWriteStr
//#define TEST_ShowRead_01Str
//#define TEST_ShowReadSearchList
//#define TEST_ShowOriginData
#include
#include
#include
#include
#define Line 200
#define ErrOpen(filename); { printf("文件%s打开失败!\n",filename); exit(1);}
typedef struct
{
int hufnode;
}DataType;
typedef struct HTNode
{
DataType data;
int weight;
int lchild,rchild,parent;
}HTNode,*HTree;
typedef char **HuffmanCode;
/*全局变量*/
char fOriginFile[]="1.txt";
char fDataTimes[]="DataTimes.txt";
char fCompress[]="Compress.txt";
char fHuffmanCode[]="HuffmanCode.txt";
char fDeCompress[]="DeCompress.txt";
int HexDataTimes[0xFF+1]={0};
/*模块一*/
void ReadByte_WriteHex(const char *filename,const char *SaveName=fDataTimes);
void memset_int(int *num,int value,int size);
void TransToHex(char *ByteData,int *HexData,int bytenum);
void AddTimes(int *HexDataTimes,int *HexData);
/*模块二*/
void ReadDataTimes(int *HexDataTimes,const char *filename=fDataTimes);
void CreateHTree(HTree &HT,int &N);
void SelectMin(HTree HT,const int n,int &min1,int &min2);
void HuffmanCoding(HTree HT,HuffmanCode &HC,int n);
/*模块三*/
void CreateSearchList(HuffmanCode &HC,HuffmanCode &SearchList);
void Compress(HuffmanCode SearchList);
void _01StrCat(char *_01Str,char *OriginCode,int strlen,HuffmanCode SearchList);
void TransWrite(char *_01Str,int _01StrLen);
inline void Set_bit(char &a,int bit,int _01);
void WriteHuffmanCode(HuffmanCode SearchList,char *_01Str);
/*模块四*/
void DeCompress();
void BitToStr(char a,char *Each_Str);
void TransWrite2(char *_01Str,char SearchList[0xFF+1][100],int byetnum);
int main(void)
{
printf("男同专用压缩软件启动!\n");
ReadByte_WriteHex(fOriginFile);
int N;
HTree HT=NULL;
HuffmanCode HC=NULL;
CreateHTree(HT,N);//CreateHTree将会改变N的值
HuffmanCoding(HT,HC,N);
HuffmanCode SearchList=NULL;
CreateSearchList(HC,SearchList);
Compress(SearchList);
DeCompress();
}
/*模块一*///###############################################################
void ReadByte_WriteHex(const char *filename,const char *SaveName)
{
int i,bytenum;
FILE *pfile=NULL,*pfile_2=NULL;
if( (pfile=fopen(filename,"rb")) ==NULL)
ErrOpen(filename);
if( (pfile_2=fopen(SaveName,"ab")) ==NULL)
{
fclose(pfile);
ErrOpen(SaveName);
}
char ByteData[Line+1]={0};
int HexData[Line+1]={0};
for(;feof(pfile)==0;)
{
memset_int(HexData,-1,sizeof(HexData)/sizeof(int));//每次都重置
memset(ByteData,0,sizeof(ByteData)/sizeof(char));
bytenum=fread(ByteData,sizeof(char),Line,pfile);
#ifdef TEST_ShowOriginData
printf("%s\n\n\n\n\n",ByteData);
getchar();
#endif
TransToHex(ByteData,HexData,bytenum);//ByteData转换成16进制数存储
AddTimes(HexDataTimes,HexData);
}
for(i=0;i<=0xFF;++i)
fprintf(pfile_2,"%02X==%010d\n",i,HexDataTimes[i]);
fclose(pfile);
fclose(pfile_2);
}
void memset_int(int *num,int value,int size)
{
for(int i=0;i<size;++i)
num[i]=value;
}
void TransToHex(char *ByteData,int *HexData,int bytenum)
{
for(int i=0;i<bytenum;++i)
HexData[i]=ByteData[i]&0b11111111;
//抹去高位,保留低位
}
void AddTimes(int *HexDataTimes,int *HexData)
{
for(int i=0;HexData[i]!=-1;++i)
++HexDataTimes[ HexData[i] ];
}
//###############################################################/*模块一*/
/*模块二*///###############################################################
void ReadDataTimes(int *HexDataTimes,const char *filename)
{
FILE *pfile=NULL;
if((pfile=fopen(filename,"rb"))==NULL)
ErrOpen(filename);
for(int i=0;i<0xFF+1;++i)
fscanf(pfile,"%02X==%010d",&HexDataTimes[i],&HexDataTimes[i]);
//这里重复赋值,是因为发现VS2019不支持*赋值跳过,就重复赋值覆盖了
fclose(pfile);
}
void CreateHTree(HTree &HT,int &N)
{
int i,n,m;
int min1,min2;
ReadDataTimes(HexDataTimes);
int weight[0xFF+1],weight_Hex[0xFF+1];
for(i=0,n=0;i<0xFF+1;++i)
{
if(HexDataTimes[i]!=0)
{
weight[n]=HexDataTimes[i];//把非0权值提取出来
weight_Hex[n]=i; //保留对应非0权值的映射值
++n;
}
}
N=n;
m=2*n-1;//weight[0~n-1]存放了所以非0权值
HT=(HTree)malloc((m+1)*sizeof(HTNode));//分配编码空间舍去0单元不用
for(i=0;i<=m;++i)
HT[i]={{0},0,0,0};//初始化
for(i=1;i<=n;++i)
HT[i].weight=weight[i-1];//这里错开一位,因为weight[]是从0开始存放的
for(i=n+1;i<=m;++i)
{
SelectMin(HT,i-1,min1,min2);
HT[min1].parent=i;
HT[min2].parent=i;
HT[i].lchild=min1;
HT[i].rchild=min2;
HT[i].weight=HT[min1].weight+HT[min2].weight;
}
}
void SelectMin(HTree HT,const int n,int &min1,int &min2)
{
int i,min_value=INT_MAX;
for(i=1;i<=n;++i)
if(HT[i].parent==0 && HT[i].weight<min_value)
{
min_value=HT[i].weight;
min1=i;
}
for(i=1,min_value=INT_MAX;i<=n;++i)
if(HT[i].parent==0 && HT[i].weight<min_value &&i!=min1)
{
min_value=HT[i].weight;
min2=i;
}
}
void HuffmanCoding(HTree HT,HuffmanCode &HC,int n)
{
int start,parent,current;
HC=(HuffmanCode)malloc( (n+1) *sizeof(char*) );
char *Each_Code=(char*)malloc( n*sizeof(char) );
Each_Code[n-1]='\0';
for(int i=1;i<=n;++i)
{
start=n-1;//从start开始时指向最后,即编码结束符位置
current=i;
parent=HT[i].parent;
while(parent!=0)
{
--start;
if(HT[parent].lchild==current)
Each_Code[start]='0';
else
Each_Code[start]='1';
current=parent;
parent=HT[parent].parent;
}
HC[i]=(char *)malloc( (n-start) *sizeof(char) );
strcpy(HC[i],&Each_Code[start]);
}
free(Each_Code);
}
//###############################################################/*模块二*/
/*模块三*///###############################################################
void CreateSearchList(HuffmanCode &HC,HuffmanCode &SearchList)
{
int i, j;
SearchList = (HuffmanCode)calloc(0xFF+1, sizeof(char*));
for (i = 0; i <= 0xFF; ++i)
SearchList[i] = NULL;
for (i = 0, j = 1; i <= 0xFF; ++i)
{
if (HexDataTimes[i] != 0)
{
SearchList[i] = HC[j];
++j;
}
}
}
void Compress(HuffmanCode SearchList)
{
FILE *pfile=NULL;
if((pfile=fopen(fOriginFile,"rb"))==NULL)
ErrOpen(fOriginFile)
char OriginCode[Line+10]={0},_01Str[10*Line]={0};
for(;feof(pfile)==0;)
{
memset(OriginCode,0,strlen(OriginCode));
fread(OriginCode,sizeof(char),Line,pfile);
_01StrCat(_01Str,OriginCode,strlen(OriginCode),SearchList);
TransWrite(_01Str,strlen(_01Str));
}
fclose(pfile);
WriteHuffmanCode(SearchList,_01Str);
}
void _01StrCat(char *_01Str,char *OriginCode,int strlen,HuffmanCode SearchList)
{
int i;
for(i=0;i<strlen;++i)
{
strcat(_01Str,SearchList[ OriginCode[i]&0b11111111 ]);
#ifdef TEST_ShowWriteStr
printf("%s",SearchList[ OriginCode[i]&0b11111111 ]);
#endif
}
}
void TransWrite(char *_01Str,int _01StrLen)
{
int i,last,bytenum;
char BitCode[_01StrLen/8+10]={0};
char temp[10];
last=_01StrLen%8;
_01StrLen-=last;
for(i=7,bytenum=0;i<_01StrLen;i+=8)
{
Set_bit(BitCode[bytenum],1,_01Str[i]-48);
Set_bit(BitCode[bytenum],2,_01Str[i-1]-48);
Set_bit(BitCode[bytenum],3,_01Str[i-2]-48);
Set_bit(BitCode[bytenum],4,_01Str[i-3]-48);
Set_bit(BitCode[bytenum],5,_01Str[i-4]-48);
Set_bit(BitCode[bytenum],6,_01Str[i-5]-48);
Set_bit(BitCode[bytenum],7,_01Str[i-6]-48);
Set_bit(BitCode[bytenum],8,_01Str[i-7]-48);
bytenum++;
}
BitCode[bytenum]='\0';
strcpy(temp,&_01Str[_01StrLen]);
strcpy(_01Str,temp);
FILE *pfile=NULL;
if((pfile=fopen(fCompress,"ab"))==NULL)
ErrOpen(fCompress)
fwrite(BitCode,sizeof(char),bytenum,pfile);
fclose(pfile);
}
inline void Set_bit(char &a,int bit,int _01)
{
char bit_to_1[9]={0, 0b1 , 0b10, 0b100, 0b1000, 0b10000,
0b100000, 0b1000000, (char)0b10000000};
char bit_to_0[9]={0,(char)0b11111110,(char)0b11111101,(char)0b11111011,(char)0b11110111
,(char)0b11101111,(char)0b11011111,(char)0b10111111,(char)0b01111111};
if(_01==0)
{
a&=bit_to_0[bit]; //表示要将bit位变为0,也即是将第bit位&0,其他位&1即可
}
else if(_01==1)
{
a|=bit_to_1[bit];//表示要将bit位变为1,也即时将第bit位|1,其他位|0即可
}
}
void WriteHuffmanCode(HuffmanCode SearchList,char *_01Str)
{
int i;
FILE *pfile=NULL;
if((pfile=fopen(fHuffmanCode,"wb"))==NULL)
ErrOpen(fHuffmanCode);
for(i=0;i<=0xFF;++i)
fprintf(pfile,"%s\n",SearchList[i]);
fprintf(pfile,"%s",_01Str);
fclose(pfile);
}
//###############################################################/*模块三*/
/*模块四*///###############################################################
void DeCompress()
{
int i;
char surplus[12]={0};
char SearchList[0xFF+1][100];
FILE *pfile=NULL;
if((pfile=fopen(fHuffmanCode,"rb"))==NULL)
ErrOpen(fHuffmanCode)
for(i=0;i<=0xFF;++i)
{
fscanf(pfile,"%s\n",SearchList[i]);
#ifdef TEST_ShowReadSearchList
printf("%s\n",SearchList[i]);
#endif
}
fgets(surplus,10,pfile);
fclose(pfile);
for(i=0;i<=0xFF;++i)
if(strcmp(SearchList[i],"(null)")==0)
SearchList[i][0]='\0';
if((pfile=fopen(fCompress,"rb"))==NULL)
ErrOpen(fCompress)
char BitCode[Line+10],_01Str[8*Line+10]={0},EachStr[50];
int bytenum;
for(;feof(pfile)==0;)
{
memset(BitCode,0,strlen(BitCode));
bytenum=fread(BitCode,sizeof(char),Line,pfile);
for(i=0;i<bytenum;++i)
{
BitToStr(BitCode[i],EachStr);
#ifdef TEST_ShowRead_01Str
printf("%s",EachStr);
#endif;
strcat(_01Str,EachStr);
}
TransWrite2(_01Str,SearchList,bytenum);
}
fclose(pfile);
strcat(_01Str,surplus);
if((pfile=fopen(fDeCompress,"ab"))==NULL)
ErrOpen(fDeCompress);
for(i=0;i<=0xFF;++i)
{
if(SearchList[i][0]!='\0')
if(strcmp(_01Str,SearchList[i])==0)
{
char OneCode=i;
fwrite(&OneCode,sizeof(char),1,pfile);
break;
}
}
fclose(pfile);
}
void BitToStr(char a,char *Each_Str)
{
int bit[8]={0b00000001,0b00000010,0b00000100,
0b00001000,0b00010000,0b00100000,0b01000000,0b10000000};
Each_Str[0]=((a&bit[7])>> 7)+48;
Each_Str[1]=((a&bit[6])>> 6)+48;
Each_Str[2]=((a&bit[5])>> 5)+48;
Each_Str[3]=((a&bit[4])>> 4)+48;
Each_Str[4]=((a&bit[3])>> 3)+48;
Each_Str[5]=((a&bit[2])>> 2)+48;
Each_Str[6]=((a&bit[1])>> 1)+48;
Each_Str[7]=((a&bit[0])>> 0)+48;
Each_Str[8]='\0';
}
void TransWrite2(char *_01Str,char SearchList[0xFF+1][100],int bytenum)
{
FILE *pfile=NULL;
if((pfile=fopen(fDeCompress,"ab"))==NULL)
ErrOpen(fDeCompress);
int i=0,times=0,j,Bitnum=bytenum*8,len=0;
char *p=NULL;
char temp[100];
char OneCode;
while(i<=Bitnum)
{
for(j=0;j<=0xFF;++j)
{
if(SearchList[j][0]!='\0')
if(strncmp(&_01Str[i],SearchList[j],strlen(SearchList[j]))==0)
{
len+=strlen(SearchList[j]);
OneCode=j;
fwrite(&OneCode,sizeof(char),1,pfile);
#ifdef TEST_DeCompress
printf("%c",OneCode);
#endif
times++;
i+=strlen(SearchList[j]);
break;
}
}
if(j==0xFF+1)
++i;
}
strcpy(_01Str,&_01Str[len]);
fclose(pfile);
}
//###############################################################/*模块四*/
void ReadByte_WriteHex(const char *filename,const char *SaveName)
{
int i,bytenum;
FILE *pfile=NULL,*pfile_2=NULL;
if( (pfile=fopen(filename,"rb")) ==NULL)
ErrOpen(filename);
if( (pfile_2=fopen(SaveName,"ab")) ==NULL)
{
fclose(pfile);
ErrOpen(SaveName);
}
//打开文件
char ByteData[Line+1]={0};//存储原始字节数据
int HexData[Line+1]={0};//以16进制存储字节值的
for(;feof(pfile)==0;)
{
memset_int(HexData,-1,sizeof(HexData)/sizeof(int));//每次都重置为-1
memset(ByteData,0,sizeof(ByteData)/sizeof(char));
//重置数组为0
bytenum=fread(ByteData,sizeof(char),Line,pfile);
#ifdef TEST_ShowOriginData//自调试宏定义
printf("%s\n\n\n\n\n",ByteData);
getchar();
#endif
TransToHex(ByteData,HexData,bytenum);//将ByteData转换成16进制数存储
AddTimes(HexDataTimes,HexData);
//将16进制字节值的出现次数存储到HexDataTime[0~255]全局变量中
}
for(i=0;i<=0xFF;++i)
fprintf(pfile_2,"%02X==%010d\n",i,HexDataTimes[i]);
//以特定格式将频次数据写入文件
fclose(pfile);
fclose(pfile_2);
/*自定义memset函数,因为memset只能以0、-1来初始化,备不时之需*/
void memset_int(int *num,int value,int size)
{
for(int i=0;i<size;++i)
num[i]=value;
}
/*按位运算,将字节值转换成16进制方便使用*/
void TransToHex(char *ByteData,int *HexData)
{
for(int i=0;i<bytenum;++i)
HexData[i]=ByteData[i]&0b11111111;
//抹去高位,保留低位
//任何bit位 &0为0、|1为1
}
/*将出现字节值计次到全局变量HexDataTimes[0~0xFF]*/
void AddTimes(int *HexDataTimes,int *HexData)
{
for(int i=0;HexData[i]!=-1;++i)
++HexDataTimes[ HexData[i] ];
}
/*模块二*///###############################################################
/*读取字节值的频次,作为权值*/
void ReadDataTimes(int *HexDataTimes,const char *filename)
{
FILE *pfile=NULL;
if((pfile=fopen(filename,"rb"))==NULL)
ErrOpen(filename);
for(int i=0;i<0xFF+1;++i)
fscanf(pfile,"%02X==%010d",&HexDataTimes[i],&HexDataTimes[i]);
//这里重复赋值,是因为发现VS2019不支持*赋值跳过,就重复赋值覆盖了
fclose(pfile);
}
/*建立哈夫曼树*/
void CreateHTree(HTree &HT,int &N)
{
int i,n,m;
int min1,min2;
ReadDataTimes(HexDataTimes);
int weight[0xFF+1],weight_Hex[0xFF+1];
for(i=0,n=0;i<0xFF+1;++i)
{
if(HexDataTimes[i]!=0)
{
weight[n]=HexDataTimes[i];//把非0权值提取出来
weight_Hex[n]=i; //保留对应非0权值的映射值
++n;
}
}
N=n;
m=2*n-1;//weight[0~n-1]存放了所以非0权值
HT=(HTree)malloc((m+1)*sizeof(HTNode));//分配编码空间舍去0单元不用
for(i=0;i<=m;++i)
HT[i]={{0},0,0,0};//初始化
for(i=1;i<=n;++i)
HT[i].weight=weight[i-1];//这里错开一位,因为weight[]是从0开始存放的
for(i=n+1;i<=m;++i)
{
SelectMin(HT,i-1,min1,min2);
HT[min1].parent=i;
HT[min2].parent=i;
HT[i].lchild=min1;
HT[i].rchild=min2;
HT[i].weight=HT[min1].weight+HT[min2].weight;
}
}
/*哈夫曼建树辅助函数*/
void SelectMin(HTree HT,const int n,int &min1,int &min2)
{
int i,min_value=INT_MAX;
for(i=1;i<=n;++i)
if(HT[i].parent==0 && HT[i].weight<min_value)
{
min_value=HT[i].weight;
min1=i;
}
for(i=1,min_value=INT_MAX;i<=n;++i)
if(HT[i].parent==0 && HT[i].weight<min_value &&i!=min1)
{
min_value=HT[i].weight;
min2=i;
}
}
/*根据哈弗曼树进行编码*/
void HuffmanCoding(HTree HT,HuffmanCode &HC,int n)
{
int start,parent,current;
HC=(HuffmanCode)malloc( (n+1) *sizeof(char*) );
char *Each_Code=(char*)malloc( n*sizeof(char) );
Each_Code[n-1]='\0';
for(int i=1;i<=n;++i)
{
start=n-1;//从start开始时指向最后,即编码结束符位置
current=i;
parent=HT[i].parent;
while(parent!=0)
{
--start;
if(HT[parent].lchild==current)
Each_Code[start]='0';
else
Each_Code[start]='1';
current=parent;
parent=HT[parent].parent;
}
HC[i]=(char *)malloc( (n-start) *sizeof(char) );
strcpy(HC[i],&Each_Code[start]);
}
free(Each_Code);
}
//###############################################################/*模块二*/
/*模块三*///###############################################################
/*HC[1~N]中的编码串指针赋给*SearchList[0~0xFF]中*/
/*两者区别:SearchList中有NULL,而HC只有[0]单元为空*/
void CreateSearchList(HuffmanCode &HC,HuffmanCode &SearchList)
{
int i, j;
SearchList = (HuffmanCode)calloc(0xFF+1, sizeof(char*));
for (i = 0; i <= 0xFF; ++i)
SearchList[i] = NULL;
for (i = 0, j = 1; i <= 0xFF; ++i)
{
if (HexDataTimes[i] != 0)
{
SearchList[i] = HC[j];
++j;
}
}
}
/*根据编码查询表SearchList[0~0xFF]进行编码*/
void Compress(HuffmanCode SearchList)
{
FILE *pfile=NULL;
if((pfile=fopen(fOriginFile,"rb"))==NULL)
ErrOpen(fOriginFile)
char OriginCode[Line+10]={0},_01Str[10*Line]={0};
for(;feof(pfile)==0;)
{
memset(OriginCode,0,strlen(OriginCode));
fread(OriginCode,sizeof(char),Line,pfile);
_01StrCat(_01Str,OriginCode,strlen(OriginCode),SearchList);
TransWrite(_01Str,strlen(_01Str));//写入BitCode
}
fclose(pfile);
WriteHuffmanCode(SearchList,_01Str);
//将编码查询表写入另一文件,同时将无法凑足8bit位的
//编码以字符串形式写到该文件末尾
}
/*辅助函数,将读取的字节值对应的哈夫曼编码逐个连接到_01Str*/
void _01StrCat(char *_01Str,char *OriginCode,int strlen,HuffmanCode SearchList)
{
int i;
for(i=0;i<strlen;++i)
{
strcat(_01Str,SearchList[ OriginCode[i]&0b11111111 ]);
#ifdef TEST_ShowWriteStr
printf("%s",SearchList[ OriginCode[i]&0b11111111 ]);
#endif
}
}
void TransWrite(char *_01Str,int _01StrLen)
{
/*将_01Str(0101010000111……)字符数字数值存储到BitCode[]元素的bit位中*/
int i,last,bytenum;
char BitCode[_01StrLen/8+10]={0};
char temp[10];
last=_01StrLen%8;//将不足八位的截断
_01StrLen-=last;
for(i=7,bytenum=0;i<_01StrLen;i+=8)
{
Set_bit(BitCode[bytenum],1,_01Str[i]-48);
Set_bit(BitCode[bytenum],2,_01Str[i-1]-48);
Set_bit(BitCode[bytenum],3,_01Str[i-2]-48);
Set_bit(BitCode[bytenum],4,_01Str[i-3]-48);
Set_bit(BitCode[bytenum],5,_01Str[i-4]-48);
Set_bit(BitCode[bytenum],6,_01Str[i-5]-48);
Set_bit(BitCode[bytenum],7,_01Str[i-6]-48);
Set_bit(BitCode[bytenum],8,_01Str[i-7]-48);
bytenum++;
}
BitCode[bytenum]='\0';
strcpy(temp,&_01Str[_01StrLen]);
strcpy(_01Str,temp);//将截断未被写入的子字符串恢复到_01Str开头
FILE *pfile=NULL;
if((pfile=fopen(fCompress,"ab"))==NULL)
ErrOpen(fCompress)
fwrite(BitCode,sizeof(char),bytenum,pfile);
fclose(pfile);
}
/*按位运算设定bit位,稍大数值用强制类型转换(char),否则部分编译器报错数值溢出*/
inline void Set_bit(char &a,int bit,int _01)
{
char bit_to_1[9]={0, 0b1 , 0b10, 0b100, 0b1000, 0b10000,
0b100000, 0b1000000, (char)0b10000000};
char bit_to_0[9]={0,(char)0b11111110,(char)0b11111101,(char)0b11111011,(char)0b11110111
,(char)0b11101111,(char)0b11011111,(char)0b10111111,(char)0b01111111};
if(_01==0)
{
a&=bit_to_0[bit]; //表示要将bit位变为0,也即是将第bit位&0,其他位&1即可
}
else if(_01==1)
{
a|=bit_to_1[bit];//表示要将bit位变为1,也即时将第bit位|1,其他位|0即可
}
}
/*将哈夫曼编码表写到文件fHuffmanCode,格式为每行一个编码*/
void WriteHuffmanCode(HuffmanCode SearchList,char *_01Str)
{
int i;
FILE *pfile=NULL;
if((pfile=fopen(fHuffmanCode,"wb"))==NULL)
ErrOpen(fHuffmanCode);
for(i=0;i<=0xFF;++i)
fprintf(pfile,"%s\n",SearchList[i]);
fprintf(pfile,"%s",_01Str);//不足8bit位的,写到文件尾部
fclose(pfile);
}
//###############################################################/*模块三*/
/*模块四*///###############################################################
void DeCompress()
{
int i;
char surplus[12]={0};//读取编码查询表文件末尾的不足8位字符串
char SearchList[0xFF+1][100];//这里以数组形式使用编码表
FILE *pfile=NULL;
if((pfile=fopen(fHuffmanCode,"rb"))==NULL)
ErrOpen(fHuffmanCode)
for(i=0;i<=0xFF;++i)
{
//自定义宏调试
fscanf(pfile,"%s\n",SearchList[i]);
#ifdef TEST_ShowReadSearchList
printf("%s\n",SearchList[i]);
#endif
}
fgets(surplus,10,pfile);//最后一行就是不足8位的字符串,读取它
fclose(pfile);
//编码查询表写入时有的为(null),将其截断
for(i=0;i<=0xFF;++i)
if(strcmp(SearchList[i],"(null)")==0)
SearchList[i][0]='\0';
if((pfile=fopen(fCompress,"rb"))==NULL)
ErrOpen(fCompress)
char BitCode[Line+10],_01Str[8*Line+10]={0},EachStr[50];
int bytenum;
for(;feof(pfile)==0;)
{
memset(BitCode,0,strlen(BitCode));
bytenum=fread(BitCode,sizeof(char),Line,pfile);
//返回值为读取了的字节数
for(i=0;i<bytenum;++i)
{
//BitCode转为字符串
BitToStr(BitCode[i],EachStr);
#ifdef TEST_ShowRead_01Str
printf("%s",EachStr);
#endif;
//将每个逆转得到的字符串连接到_01Str
strcat(_01Str,EachStr);
}
//将_01Str写入到解压文件
TransWrite2(_01Str,SearchList,bytenum);
}
fclose(pfile);
//下面的代码作用:将不足8位的编码字符串连接到_01Str中,
//按理将正好凑足8位。并将该(也是最后一个)字节写入解压文件
strcat(_01Str,surplus);
if((pfile=fopen(fDeCompress,"ab"))==NULL)
ErrOpen(fDeCompress);
for(i=0;i<=0xFF;++i)
{
if(SearchList[i][0]!='\0')
if(strcmp(_01Str,SearchList[i])==0)
{
char OneCode=i;
fwrite(&OneCode,sizeof(char),1,pfile);
break;
}
}
fclose(pfile);
}
/*基本操作,恕不解释*/
void BitToStr(char a,char *Each_Str)
{
int bit[8]={0b00000001,0b00000010,0b00000100,
0b00001000,0b00010000,0b00100000,0b01000000,0b10000000};
Each_Str[0]=((a&bit[7])>> 7)+48;
Each_Str[1]=((a&bit[6])>> 6)+48;
Each_Str[2]=((a&bit[5])>> 5)+48;
Each_Str[3]=((a&bit[4])>> 4)+48;
Each_Str[4]=((a&bit[3])>> 3)+48;
Each_Str[5]=((a&bit[2])>> 2)+48;
Each_Str[6]=((a&bit[1])>> 1)+48;
Each_Str[7]=((a&bit[0])>> 0)+48;
Each_Str[8]='\0';
}
void TransWrite2(char *_01Str,char SearchList[0xFF+1][100],int bytenum)
{
FILE *pfile=NULL;
if((pfile=fopen(fDeCompress,"ab"))==NULL)
ErrOpen(fDeCompress);
int i=0,times=0,j,Bitnum=bytenum*8,len=0;
char *p=NULL;
char temp[100];
char OneCode;
//循环遍历_01Str
while(i<=Bitnum)
{
for(j=0;j<=0xFF;++j)//遍历编码查询表
{
//如果编码串不为空
if(SearchList[j][0]!='\0')
//比较 编码串长度个字符
if(strncmp(&_01Str[i],SearchList[j],strlen(SearchList[j]))==0)
{ //如果_01Str[i]开始匹配成功,
len+=strlen(SearchList[j]);//统计总共写入了的_01Str中的字符数
OneCode=j;//j的值即为该编码串对应的原字节值
//把字节值写入解压文件,完成一个字节的解压
//自定义宏调试
fwrite(&OneCode,sizeof(char),1,pfile);
#ifdef TEST_DeCompress
printf("%c",OneCode);
#endif
times++;
i+=strlen(SearchList[j]);
//执行到这里说明已经解压了编码串长度个字符,i要随之变动
break;
}
}
if(j==0xFF+1)//说明遍历了编码查询表也没有匹配成功,继续从下一个字符匹配
++i;//所以加1
}
strcpy(_01Str,&_01Str[len]);//将未解压的子字符串安排到开头,下一次继续
fclose(pfile);
}
//###############################################################/*模块四*/
模块调用层次非常清晰,不多做解释
int main(void)
{
printf("男同专用压缩软件启动!\n");//个性化
ReadByte_WriteHex(fOriginFile);
int N;
HTree HT=NULL;
HuffmanCode HC=NULL;
CreateHTree(HT,N);//CreateHTree将会改变N的值
HuffmanCoding(HT,HC,N);
HuffmanCode SearchList=NULL;
CreateSearchList(HC,SearchList);
Compress(SearchList);
DeCompress();
}
#include
#include
#define ErrOpen(filename){printf("文件%s打开失败\n",filename); exit(1);}
int main(void)
{
char fOriginFile[]="1.txt",
fCompress[]="2.txt";
FILE *pfile_1=NULL,*pfile_2=NULL;
if((pfile_1=fopen(fOriginFile,"rb"))==NULL)
ErrOpen(fOriginFile)
if((pfile_2=fopen(fCompress,"rb"))==NULL)
{
fclose(pfile_1);
ErrOpen(fCompress);
}
char Byte1,Byte2;
int i;
for(i=0;;)
{
if(feof(pfile_1)==0&&feof(pfile_2)==0)
{
fread(&Byte1,sizeof(char),1,pfile_1);
fread(&Byte2,sizeof(char),1,pfile_2);
if(Byte1!=Byte2)
i++;
printf("进行中!\n");
}
else
break;
}
fclose(pfile_1);
fclose(pfile_2);
printf("\n\n\n有%d个字节不同",i);
getchar();
}
解压还原率:100%
占原文件大小:71%
还试了一个压缩gif的,解压时候出了问题,有空再去改它。
更新日期:2020/6/18
美化什么的暂时先不搞,我要先挑战100M100%解压还原,然后估计一下数据量看看能不能搞1个G的