详解C语言实现哈夫曼编码压缩

更新

这个项目是大一写的,现在大三了,将它重新实现了一下,比原来的看起来更干净一点。

新的链接在这里手把手教你C语言哈夫曼压缩/解压缩

下面的都是以前写的,比较笨拙,但保留了原始的思路。

如果你想了解思路,可以看下面讲的细节(屎山)。
如果你水平比较高,不想看废话,可以看新实现的那篇。


最终状态:

现在已经可以对 1G任意文件(因为我只试了1G,再大的话有点费时间,足以说明代码是没大问题的)压缩后100%解压还原,文本压缩后约占原来70%~76% ,代码改动挺多的,下方的思路依然是正确的,只是代码有些bug(下方的代码只能100MB左右无误),

最新文件(更新日期2021/6/28 10:48)

至此我要结束我的课设了下面的代码也不太想改动了这里放个文件提取码:qw77里面有三个版本:
1.调试版(这是给想了解过程的人看的),因为这个代码想用IDE调试简直是做梦,这出错率至少千万分之一,调不出来的,我都是拿宏调试+合理猜测推断,真的很不容易(从最初的150字节无误到现在的至少1G无误,鬼知道我经历了什么)。
2.无界面版,这个和调试版代码一样,只是不带宏,更整洁一些,DevCpp编译可通过。
3.带界面版,这个是带点花样的,用了基于对话框的win窗体程序(CodeBlocks的,不过好像复制到VS2019项目也能跑),还有音效、个性图标,给截个图看下
详解C语言实现哈夫曼编码压缩_第1张图片

详解C语言实现哈夫曼编码压缩_第2张图片
修改部分的代码,有的是bug(循环的判断与边界情况、数组分配过小越界),还有的是为了效率提升(主要是频繁打开文件的)。

文章目录

  • 更新
  • 最终状态:
  • 最新文件(更新日期2021/6/28 10:48)
  • 目标:
  • 必备知识:
  • 思路
  • 细节流程图
    • 模块一
    • 模块二
    • 模块三
    • 模块四
  • 文件展示(样例)
    • fOriginFile
    • fDataTimes
    • fHuffmanCode
    • fCompress
    • fDeCompress
  • 核心代码
  • 讲解
    • 模块一:
    • 模块二:
    • 模块三:
    • 模块四:
    • 主函数:
  • 效果展示
    • 简单校验程序
    • 例一:朱自清《春》
      • 压缩前:
      • 压缩后:
    • 例二:《离骚》全文
      • 压缩前:
      • 压缩后:

目标:

能对任意格式的文件进行压缩

必备知识:

1.哈夫曼树的编码
2.C语言文件操作、按位运算(压榨存储空间的关键)

IDE:CodeBlocks

===========================================================

思路

关键点

模块一
计算机以字节为最小单位进行数据存储、一个字节8bit位,要做到对任意格式的压缩,映射是关键,也就是把字节数据映射成一个值(0b00000000~0b11111111),然后对它编码。
模块二
明确编码对象:字节值(0~0xFF),权值:字节值频次
模块三
经实践,决定此处将编码表与压缩文件分别存放
模块四:解压缩。

流程

模块 任务 结果
模块一 统计文件中字节值频次,字节值作为编码对象,频次作为哈夫曼编码的权值 得到权值
模块二 利用权值对字节值进行哈夫曼编码 得到编码表
模块三 利用编码表改写文件实现压缩 得到压缩文件
模块四 利用编码表解压缩 实现解压缩
本人评估
模块 难度
★★
★★★★★
★★★

细节流程图

模块一

详解C语言实现哈夫曼编码压缩_第3张图片

模块二

详解C语言实现哈夫曼编码压缩_第4张图片

模块三

详解C语言实现哈夫曼编码压缩_第5张图片
(点击图片放大)

模块四

详解C语言实现哈夫曼编码压缩_第6张图片
(点击图片放大)

文件展示(样例)

fOriginFile

详解C语言实现哈夫曼编码压缩_第7张图片

fDataTimes

详解C语言实现哈夫曼编码压缩_第8张图片

fHuffmanCode

详解C语言实现哈夫曼编码压缩_第9张图片

fCompress

详解C语言实现哈夫曼编码压缩_第10张图片

fDeCompress

详解C语言实现哈夫曼编码压缩_第11张图片

(讲解部分有详细注释)

核心代码

//#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();
}

例一:朱自清《春》

详解C语言实现哈夫曼编码压缩_第12张图片

压缩前:

详解C语言实现哈夫曼编码压缩_第13张图片

压缩后:

详解C语言实现哈夫曼编码压缩_第14张图片
解压还原率:100%
占原文件大小:74%

例二:《离骚》全文

详解C语言实现哈夫曼编码压缩_第15张图片

压缩前:

详解C语言实现哈夫曼编码压缩_第16张图片

压缩后:

详解C语言实现哈夫曼编码压缩_第17张图片

解压还原率:100%
占原文件大小:71%

还试了一个压缩gif的,解压时候出了问题,有空再去改它。
更新日期:2020/6/18
美化什么的暂时先不搞,我要先挑战100M100%解压还原,然后估计一下数据量看看能不能搞1个G的

你可能感兴趣的:(小项目,c语言,数据结构,c++,信息压缩)