概述
更新
这个项目是大一写的,现在大三了,将它重新实现了一下,比原来的看起来更干净一点。
新的链接在这里手把手教你C语言哈夫曼压缩/解压缩
下面的都是以前写的,比较笨拙,但保留了原始的思路。
如果你想了解思路,可以看下面讲的细节(屎山)。
如果你水平比较高,不想看废话,可以看新实现的那篇。
最终状态:
现在已经可以对 1G任意文件(因为我只试了1G,再大的话有点费时间,足以说明代码是没大问题的)压缩后100%解压还原,文本压缩后约占原来70%~76% ,代码改动挺多的,下方的思路依然是正确的,只是代码有些bug(下方的代码只能100MB左右无误),
最新文件(更新日期2021/6/28 10:48)
至此我要结束我的课设了,下面的代码也不太想改动了,这里放个文件提取码:qw77里面有三个版本:
1.调试版(这是给想了解过程的人看的),因为这个代码想用IDE调试简直是做梦,这出错率至少千万分之一,调不出来的,我都是拿宏调试+合理猜测推断,真的很不容易(从最初的150字节无误到现在的至少1G无误,鬼知道我经历了什么)。
2.无界面版,这个和调试版代码一样,只是不带宏,更整洁一些,DevCpp编译可通过。
3.带界面版,这个是带点花样的,用了基于对话框的win窗体程序(CodeBlocks的,不过好像复制到VS2019项目也能跑),还有音效、个性图标,给截个图看下
修改部分的代码,有的是bug(循环的判断与边界情况、数组分配过小越界),还有的是为了效率提升(主要是频繁打开文件的)。
文章目录
- 更新
- 最终状态:
- 最新文件(更新日期2021/6/28 10:48)
- 目标:
- 必备知识:
- 思路
- 细节流程图
- 模块一
- 模块二
- 模块三
- 模块四
- 文件展示(样例)
- fOriginFile
- fDataTimes
- fHuffmanCode
- fCompress
- fDeCompress
- 核心代码
- 讲解
- 模块一:
- 模块二:
- 模块三:
- 模块四:
- 主函数:
- 效果展示
- 简单校验程序
- 例一:朱自清《春》
- 压缩前:
- 压缩后:
- 例二:《离骚》全文
- 压缩前:
- 压缩后:
目标:
能对任意格式的文件进行压缩
必备知识:
1.哈夫曼树的编码
2.C语言文件操作、按位运算(压榨存储空间的关键)
IDE:CodeBlocks
===========================================================
思路
关键点
模块一:
计算机以字节为最小单位进行数据存储、一个字节8bit位,要做到对任意格式的压缩,映射是关键,也就是把字节数据映射成一个值(0b00000000~0b11111111),然后对它编码。
模块二:
要明确编码对象:字节值(0~0xFF),权值:字节值频次
模块三:
经实践,决定此处将编码表与压缩文件分别存放。
模块四:解压缩。
流程
模块 | 任务 | 结果 |
---|---|---|
模块一 | 统计文件中字节值频次,字节值作为编码对象,频次作为哈夫曼编码的权值 | 得到权值 |
模块二 | 利用权值对字节值进行哈夫曼编码 | 得到编码表 |
模块三 | 利用编码表改写文件实现压缩 | 得到压缩文件 |
模块四 | 利用编码表解压缩 | 实现解压缩 |
本人评估 | ||
模块 | 难度 | |
– | – | |
一 | ★ | |
二 | ★★ | |
三 | ★★★★★ | |
四 | ★★★ |
细节流程图
模块一
模块二
模块三
(点击图片放大)
模块四
(点击图片放大)
文件展示(样例)
fOriginFile
fDataTimes
fHuffmanCode
fCompress
fDeCompress
(讲解部分有详细注释)
核心代码
//#define TEST_ShowWriteStr
//#define TEST_ShowRead_01Str
//#define TEST_ShowReadSearchList
//#define TEST_ShowOriginData
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#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("%snnnnn",ByteData);
getchar();
#endif
TransToHex(ByteData,HexData,bytenum);//ByteData转换成16进制数存储
AddTimes(HexDataTimes,HexData);
}
for(i=0;i<=0xFF;++i)
fprintf(pfile_2,"%02X==%010dn",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]='