⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 huffman.c

📁 用huffman编码定理对文件进行压缩与解压缩
💻 C
📖 第 1 页 / 共 2 页
字号:
输入:
		FILE* fpDst		压缩文件的文件指针
输出:
------------------------------------------------------------*/
void WriteHfmFileHead(FILE* fpDst)
{
	int secNum = 0;
	unsigned char flag = 0;

	_ASSERT(fpDst != NULL);

	// 写Huffman文件标识符
	fwrite(&HFM_FILE_TOKEN, sizeof(char), strlen(HFM_FILE_TOKEN), fpDst);

	if((secNum = SuitRunLen()) != 0)
	{
		flag = 0x40;			// 第7位置0,代表对信源文件进行压缩
								// 第6位置1,代表采用行程方式存储频次
		fputc(flag, fpDst);
		SaveFrqRunLen(fpDst, secNum);
	}
	else
	{
		flag = 0x00;			// 第7位置0,代表对信源文件进行压缩
								// 第6位置0,代表采用顺序方式存储频次
		fputc(flag, fpDst);
		SaveFrqSerial(fpDst);
	}
}

/*------------------------------------------------------------
目的:	打印压缩结果。包括信源文件长度,目标文件长度和压缩率。
输入:
		long  flenSrc		信源文件长度
		long  flenDst		目标文件长度
输出:
------------------------------------------------------------*/
void PrintResult(long flenSrc, long flenDst)
{
	printf("\n\n压缩结果:\n");
	printf("---------------------------------------------\n");
	printf("原始文件:\t%s\t%1d 字节\n", srcFile, flenSrc);
	printf("目标文件:\t%s\t%1d 字节\n", dstFile, flenDst);
	printf("---------------------------------------------\n");
	printf("压缩率:\t%.2f %%\n", (double)flenDst * 100.0 / (double)flenSrc);
}

/*------------------------------------------------------------
目的:	Huffman解压缩。
输入:
输出:
------------------------------------------------------------*/
void HufDecompress(void)
{
	int ch = 0;
	unsigned char mask  = 0x80;				// 取最高位的掩码
	unsigned char HFour = 0xf0;				// 取高4位的掩码
	FILE *fpSrc = NULL, *fpDst = NULL;

	if((fpSrc=fopen(srcFile, "rb")) == NULL)
		Error("%s 文件找不到!\n", srcFile);

	if(IsHFMFile(fpSrc) != 1)
	{
		fclose(fpSrc);
		Error("%s 压缩文件格式不正确!\n", srcFile);
	}

	ch = getc(fpSrc);						// 读取FLAG字段
	
	// 对FLAG 字段进行合法性校验,参考设计文档
	if(((ch & HFour) != 0x80)
		&& ((ch & HFour) != 0x40) 
		&& ((ch & HFour) != 0x00))
	{
		fclose(fpSrc);
		Error("%s 压缩文件格式不正确!!\n", srcFile);
	}

	if((fpDst=fopen(dstFile, "wb")) == NULL)
	{
		fclose(fpSrc);
		Error("%s 文件创建失败!\n", dstFile);
	}

	if((ch & mask) == mask)					// 信源文件没有被压缩
	{
		while((ch=fgetc(fpSrc)) != EOF)		fputc(ch, fpDst);
		fclose(fpSrc);
		fclose(fpDst);
		return;
	}

	if(ReadFrq(fpSrc, ch) == 0)				// 频次读取错误的处理
	{
		fclose(fpSrc);
		fclose(fpDst);
		remove(dstFile);
		exit(-1);
	}

	InfoSrcAnalyze();
	InitHfmTree();
	GenHfmTree();
	GenHfmCode();
	DecodeFile(fpSrc, fpDst);

	fclose(fpSrc);
	fclose(fpDst);
}

/*------------------------------------------------------------
目的:	判断是否为合法的Huffman压缩文件。
输入:
		FILE* fpSrc	压缩文件指针
输出:
		int			是否合法
		1			合法
		0			非法
------------------------------------------------------------*/
int IsHFMFile(FILE* fpSrc)
{
	char ch[sizeof(HFM_FILE_TOKEN)] = "";

	_ASSERT(fpSrc != NULL);
	fread(&ch, sizeof(char), strlen(HFM_FILE_TOKEN), fpSrc);

	return((strcmp(&HFM_FILE_TOKEN, ch) == 0) ? 1 : 0);
}

/*------------------------------------------------------------
目的:	从压缩文件读取频次表信息。
输入:
		FILE* fpSrc		压缩文件指针
		int   flag		压缩文件的FLAG字段,用来判断频次表
						存储的方式
输出:
		int				读取是否成功
		1				成功
		0				失败
------------------------------------------------------------*/
int ReadFrq(FILE* fpSrc, int flag)
{
	int i, j, secNum, len;
	long *p = NULL;							// 频次数组操作指针
	const char mask = 0x40;					// 取FLAG字段的第六位
	int total = 0;

	_ASSERT(fpSrc != NULL);
	if((flag & mask) == mask)				// 行程模式读取频次表
	{
		secNum = fgetc(fpSrc);
		for(i=0; i<secNum; i++)
		{
			p = &frequence[fgetc(fpSrc)];
			len = fgetc(fpSrc);
			for(j=0; j<len; j++)	*(p++) = fgetc(fpSrc);
		}
	}
	else									// 顺序模式读取频次表
	{
		for(i=0; i<SNUM_MAX; i++)		frequence[i] = fgetc(fpSrc);
	}
	
	// 检验频次读取是否正确。频次不能为负,所有频次不能为0。
	for(i=0; i<SNUM_MAX; i++)
	{
		if(frequence[i] < 0)
		{
			printf("ERROR: %s 原文件格式不正确或者已损坏!\n", srcFile);
			return(0);
		}
		total += frequence[i];
	}

	if(total == 0)
	{
		printf("ERROR: %s 原文件格式不正确或者已损坏!\n", srcFile);
		return(0);
	}

#ifdef _DEBUG
	PrintFreq();
#endif
	return(1);
}

/*------------------------------------------------------------
目的:	将压缩文件中的Huffman编码还原成信源文件符号。
输入:
		FILE* fpSrc		原始文件指针
		FILE* fpDst		目标文件指针
输出:
------------------------------------------------------------*/
void DecodeFile(FILE* fpSrc, FILE* fpDst)
{
	unsigned char bit = 0x00;
	const unsigned char mask  = 0x80;		// 取最高位的掩码
	const unsigned char LFour = 0x0f;		// 取低四位的掩码
	HufNode *node = HEAD_NODE;
	int i, ch, pos = HfmTree[HEAD].w - 1;
	long fpos = 0, flen = 0, len = 0;

	_ASSERT(fpSrc != NULL);
	_ASSERT(fpDst != NULL);
	
	fpos = ftell(fpSrc);
	fseek(fpSrc, strlen(HFM_FILE_TOKEN), SEEK_SET);
	len = fgetc(fpSrc) & LFour;
	
	fseek(fpSrc, 0, SEEK_END);
	flen = ftell(fpSrc);

	fseek(fpSrc, fpos, SEEK_SET);

	while(ftell(fpSrc) < (flen-1))
	{
		ch = fgetc(fpSrc);
		for(i=0; i<CHAR_BIT; i++)
		{
			bit = ch & (mask >> i);

			MOVE_TO_LEAF;

			if(IS_LEAF_NODE)
			{
				fputc(pos, fpDst);
				node = HEAD_NODE;
			}
		}
	}
	
	// 翻译最后一个字节,最后一个字节可能没有填满
	ch  = fgetc(fpSrc);
	len = (len == 0) ? CHAR_BIT : len;
	for(i=0; i<len; i++)
	{
		bit = ch & (mask >> i);

		MOVE_TO_LEAF;

		if(IS_LEAF_NODE)
		{
			fputc(pos, fpDst);
			node = HEAD_NODE;
		}
	}
}

/*------------------------------------------------------------
目的:	打印报告。包括:信源分析、编码信息以及压缩效果。
输入:
		char* argv[]	命令行参数
输出:
------------------------------------------------------------*/
void Report(char* argv[])
{
	int i, num = 0;
	struct	_stat buf;
	long	sfLen = 0, dfLen = 0;
	double	H = 0, avgLen = 0;

	printf("\n\t\t-- REPORT --\n\n");

	if(CMD_COMPRESS)
	{
		for(i=0; i<SNUM_MAX; i++)
		{
			if(IS_SYMBOL)
			{
				num++;
				avgLen += p[i] * strlen(HfmCode[i]);
			}
		}

		printf("信源:\n");
		printf("---------------------------------------------\n");
		printf("符号个数:\t%d\n", num);
		printf("熵:\t\t%.3f bit\n", H = Entropy());
		printf("剩余度:\t%.3f\n\n", 1 - (H / (LB10 * log10(num)))); 
		
		printf("编码:\n");
		printf("---------------------------------------------\n");
		avgLen = (avgLen <= 0) ? CHAR_BIT : avgLen;
		printf("平均码长:\t%.3f bit\n", avgLen);
		printf("编码效率:\t%.2f %%\n", 100.0 * H / avgLen);
		printf("理论压缩率:\t%.2f %%\n\n", 100.0 * avgLen / CHAR_BIT);
		
		printf("压缩:\n");
		printf("---------------------------------------------\n");	
	}

	_stat(srcFile, &buf);
	sfLen = buf.st_size;
	printf("原始文件:\t%s\t%ld 字节\n", srcFile, sfLen);

	_stat(dstFile, &buf);
	dfLen = buf.st_size;
	printf("目标文件:\t%s\t%ld 字节\n", dstFile, dfLen);
	
	if(CMD_COMPRESS)
	{
		printf("节省空间:\t%ld 字节\n", sfLen - dfLen);
		printf("压缩率:\t%.2f %%\n", ((double)dfLen) * 100.0 / ((double)sfLen));
	}
}


/*------------------------------------------------------------
目的:	将信源文件打包。因为对信源文件的压缩不足以抵消存储
		频次表的开销,因此不压缩信源文件,仅仅将其封装。即
		仅仅加上Huffman文件头标识符,其他部分与信源文件的每
		个字节都完全相同。
输入:
输出:
------------------------------------------------------------*/
void WrapSrcFile(void)
{
	int ch = 0;
	FILE *fpSrc = NULL, *fpDst = NULL;
	const unsigned char flag = 0x80;		// 最高位为1,代表信源文件
											// 没有被压缩
	if((fpSrc=fopen(srcFile, "rb")) == NULL)
		Error("%s 文件找不到!\n", srcFile);

	if((fpDst=fopen(dstFile, "wb")) == NULL)
	{
		fclose(fpSrc);
		Error("%s 文件创建失败!\n", dstFile);
	}

	// 写Huffman文件标识符
	fwrite(&HFM_FILE_TOKEN, sizeof(char), strlen(HFM_FILE_TOKEN), fpDst);
	fputc(flag, fpDst);

	// 写Huffman文件主体,与信源文件的每个字节完全相同 
	while((ch=fgetc(fpSrc)) != EOF)		fputc(ch, fpDst);
	
	fclose(fpSrc);
	fclose(fpDst);
}

/*------------------------------------------------------------
目的:	按连续方式存储频次表信息。
输入:
		FILE* fpDst		压缩文件的文件指针
输出:
------------------------------------------------------------*/
void SaveFrqSerial(FILE *fpDst)
{
	int i;

	_ASSERT(fpDst != NULL);
	for(i=0; i<SNUM_MAX; i++)		fputc(frequence[i], fpDst);
}

/*------------------------------------------------------------
目的:	判断频次表是否适合用行程方式压缩。
输入:
输出:
		int			是否适合
		<>0			适合,行程段的数量
		==0			不适合
------------------------------------------------------------*/
int SuitRunLen(void)
{
	int			i;
	int			secNum			 = 0;		// 行程段的数量
	int			totalLen		 = 0;		// 行程段中频次的总数量
	const char	SPAN[]			 = "000";	// 行程段与段之间的间隙
	char		strFrq[SNUM_MAX+1] = "";		// 频次字符串
	char		*p1 = NULL, *p2  = NULL;	// 操作频次字符串的指针

	// 初始化频次字符串
	for(i=0; i<SNUM_MAX; i++)	strFrq[i] = (frequence[i]==0) ? '0':'1';

#ifdef _DEBUG
	printf("频次表的行程分析:\nstart\tlen\tfreq\n");
	printf("---------------------------------------------");
#endif

	p1 = strstr(strFrq, "1");
	while((p2 = strstr(p1, SPAN)) != NULL)
	{
		secNum++;
		totalLen += p2 - p1;

#ifdef _DEBUG
		printf("\n%0.2X\t%d    ", p1-strFrq, p2-p1);
		while(p1<p2)	printf("%5d", frequence[(p1++)-strFrq]);
#endif
		
		if((p1 = strstr(p2, "1")) == NULL) break;
	}
	
	if(p1 != NULL)
	{
		if(*p1 == '1')
		{
			secNum++;
			while(*(p1++) != EOS) totalLen++;
		}
	}

#ifdef _DEBUG
	printf("\n---------------------------------------------\n");
	printf("行程段数:\t%d\n频次总数:\t%d", secNum, totalLen);
#endif

	return(((totalLen + secNum * 2)< SNUM_MAX) ? secNum : 0);
}

/*------------------------------------------------------------
目的:	按行程方式存储频次表。
输入:
		FILE* fpDst		压缩文件的文件指针
		int   secNum	行程段的数量
输出:
------------------------------------------------------------*/
void SaveFrqRunLen(FILE *fpDst, int secNum)
{
	int			i;
	const char	SPAN[]			 = "000";	// 行程段与段之间的间隙
	char		strFrq[SNUM_MAX+1] = "";		// 频次字符串
	char		*p1 = NULL, *p2  = NULL;	// 操作频次字符串的指针

	// 初始化频次字符串
	for(i=0; i<SNUM_MAX; i++)	strFrq[i] = (frequence[i]==0) ? '0':'1';

	p1 = strstr(strFrq, "1");
	fputc(secNum, fpDst);					// 保存行程段的数量
	while((p2 = strstr(p1, SPAN)) != NULL)
	{
		fputc(p1-strFrq, fpDst);			// 保存行程段的起始位置
		fputc(p2-p1, fpDst);				// 保存行程段的长度
		
		// 保存行程段中的频次值
		while(p1<p2)	fputc(frequence[(p1++)-strFrq], fpDst);

		if((p1 = strstr(p2, "1")) == NULL)	break;
	}

	if(p1 != NULL)
	{
		if(*p1 == '1')
		{
			fputc(p1-strFrq, fpDst);		// 保存行程段的起始位置
			p2 = &strFrq[SNUM_MAX];
			fputc(p2-p1, fpDst);			// 保存行程段的长度

			while(*p1 != EOS)
			{
				fputc(frequence[(p1++)-strFrq], fpDst);
			}
		}
	}
}

/*------------------------------------------------------------
目的:	初始化全局数据,包括:频次数组、概率数组、Huffman树
		的节点数组以及码字数组。
输入:
		char* argv[]	命令行参数,用于获取信源文件和目标
						文件的文件名
输出:
------------------------------------------------------------*/
void InitData(char* argv[])
{
	int i;

	for(i=0; i<SNUM_MAX; i++)
	{
		frequence[i] = 0l;
		p[i]		 = 0;

		HfmTree[i].l = 0;
		HfmTree[i].r = 0;
		HfmTree[i].p = 0;
		HfmTree[i].w = 0;

		strset(HfmCode[i], EOS);
	}

	strcpy(srcFile, argv[2]);
	strcpy(dstFile, argv[3]);
}

/*------------------------------------------------------------
目的:	严重错误处理。在屏幕上显示错误信息,并退出执行。
输入:
		char* fmt	格式化字符串
		...			可变参数列表
输出:
------------------------------------------------------------*/
void Error(char* fmt, ...)
{
	va_list argptr;

	va_start(argptr, fmt);
	printf("ERROR: ");
	vprintf(fmt, argptr);
	va_end(argptr);
	exit(-1);
}

/*------------------------------------------------------------
目的:	计算压缩文件头部存储频次表等信息的开销。
输入:
输出:
		int		以字节为单位的开销大小
------------------------------------------------------------*/
int StoreCost(void)
{
	int			i;
	int			secNum			 = 0;		// 行程段的数量
	int			totalLen		 = 0;		// 存储总开销
	const char	SPAN[]			 = "000";	// 行程段与段之间的间隙
	char		strFrq[SNUM_MAX+1] = "";		// 频次字符串
	char		*p1 = NULL, *p2  = NULL;	// 操作频次字符串的指针

	// 初始化频次字符串
	for(i=0; i<SNUM_MAX; i++)	strFrq[i] = (frequence[i]==0) ? '0':'1';

	p1 = strstr(strFrq, "1");
	while((p2 = strstr(p1, SPAN)) != NULL)
	{
		secNum++;
		totalLen += p2 - p1;	
		if((p1 = strstr(p2, "1")) == NULL) break;
	}
	
	if(p1 != NULL)
	{
		if(*p1 == '1')
		{
			secNum++;
			while(*(p1++) != EOS) totalLen++;
		}
	}

	totalLen += secNum * 2;
	totalLen = (totalLen < SNUM_MAX) ? (totalLen + 1) : SNUM_MAX;
	totalLen += sizeof(HFM_FILE_TOKEN);

#ifdef _DEBUG
	printf("文件头部存储开销:%d 字节\n\n", totalLen);
#endif

	return(totalLen);
}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -