Huffman编码实现文件的压缩和解压缩

news2024/11/19 17:22:50

一个项目,不过处理起来也比较麻烦,配套可以和文件传输放一起

前提知识:

哈夫曼树和哈夫曼编码的概念和构建

1:n个数构成的哈夫曼树一共有2*n-1个结点=>8  -> 15

2:数字越大的数离根节点越近,越小的数离根节点越近。因为每次是挑权值最小的两个结点当叶子结点从下往上一直到根去构建哈夫曼树

“abcdefgh”这一串字符统计各个字符出现的次数,根据次数当做权重去构建哈夫曼树,每个字符都占8个比特,把a弄成哈夫曼编码例如“11111”,不过这样也是个字符串,每个'1'字符都是8bit,等于没压缩反而大了,哈夫曼编码代替源文件字符内容,再把他转成二进制,关键点就是把这个哈夫曼编码的"1"字符8bit转成二进制里面的1,这样8bit->成了1bit,这样a->8bit就转换为5bit,压缩成功

这种对于文本文件效率还行13%~17%,图片视频音频二进制的文件效率不太高,基本可以说没有

构建哈夫曼树,利用哈夫曼编码去替换

技术点:

  • 哈夫曼树构建,哈夫曼编码构建
  • 哈希映射
  • 内存对齐的问题,压缩解压缩转二进制体现
  • 位运算,是压缩解压缩弄二进制关键性逻辑
  • 文件操作

大体思路:

压缩:
1、统计字符种类及频度:ASCII码值当下标,字符频度当对应下标的值。

2、构建哈夫曼树:在没有访问过的节点中,找最小字符频度下标来构建哈夫曼树。

3、构建哈夫曼编码

4、生成配置文件,记录源文件字符种类,对应频度,用来后面解压缩用

5、生成压缩文件:把字符的哈夫曼编码以二进制形式写入目标文件中。给压缩文件前面写入源文件数据大小,解压缩时需使用根据这个去判断什么时候停止解压(循环次数)

注意内存对齐的问题

解压缩
1、根据配置文件,得到字符种类和频度。

2、构建哈夫曼树:在没有访问过的节点中,找最小字符频度下标来构建哈夫曼树。

3、生成解压缩的文件,这个时候需要用到压缩文件头部那几个字节的信息了
 

关键结构:

static const int BUFFLEN = 256;//写文件 读文件,缓冲区大小
static const int KIND = 256; // 字符种类个数
typedef int FREP[KIND];      // 频度数组


typedef unsigned char uchar;
typedef struct
{
	uchar ch;   // 字符类型
	int   frep; // 频度
}CharFrep;      //字符种类

typedef struct
{
	int total;  // 文件总的字节个数;
	int chkind; // 文件字符种类数
	CharFrep* frepdata;//指向字符种类的指针
}FileInfo;  //得到当前文件的信息

                 
                 //哈夫曼树结构
typedef struct
{
	uchar ch;
	int   cfreq; // 频度
	int   parent;
	int   leftchild;
	int   rightchild;
}hfmnode;
                     
typedef struct
{
	int chkind;  //文件字符种类数
	int sum;     //总共节点个数
	hfmnode* node;
}HuffmanTree;

struct Index_Fref//形成下标和权值的对应放进优先级队列里面
{                 //可以直接拿pair对组弄
	int index;
	int freq;
	operator int()const { return freq; }
};


                //哈夫曼编码 处理
typedef struct   
{
	uchar ch;
	char* code;
}hfmcode;               
typedef struct
{
	int chkind; //文件字符种类数
	hfmcode* coding;   //直接开辟256而不是按照chkind去开辟,为了提高效率
	                   //读到文件一个字符直接能映射到256里的下标,而不用一个个查找
}HuffMan

我就不对哈夫曼树和哈夫曼编码的结构做解释了,对于最上面俩关于文件操作的结构说明一下

static const int BUFFLEN = 256;//写文件 读文件,缓冲区大小
static const int KIND = 256; // 字符种类个数
typedef int FREP[KIND];      // 频度数组

为什么KING=256?  ASCII种类 2的8次方,直接用频度数组,数组下标对应的就是字符的ASCII,比如FREP frep[97]+=1  说明遍历到a了,出现的次数+1

我们用以二进制编辑器打开一个文件

typedef unsigned char uchar;
typedef struct
{
	uchar ch;   // 字符类型
	int   frep; // 频度
}CharFrep;      //字符种类

typedef struct
{
	int total;  // 文件总的字节个数;
	int chkind; // 文件字符种类数
	CharFrep* frepdata;//指向字符种类的指针
}FileInfo;  //得到当前文件的信息

压缩解压缩:

写文件的时候给压缩文件前四个字节写的是源文件的大小,方便后面解压根据大小去判断解压循环多少次就结束了,要不然解压到后面二进制都是0,就会多解压出来一串cccc

压缩的关键性的代码:重点还是while循环里面字符转二进制

void CompressFile(HuffManCode* phfc, FileInfo* pfileinfo, const char* inputfile, const char* outputfile)
{
	assert(phfc != nullptr);
	assert(pfileinfo != nullptr);
	assert(inputfile != nullptr);
	assert(outputfile);
	FILE* srcfile = nullptr, * destfile = nullptr;
	errno_t tag = fopen_s(&srcfile, inputfile, "rb");//打开源文件
	if (tag)
	{
		LOG("open file %s error \n", inputfile);
		exit(EXIT_FAILURE);
	}
	tag = fopen_s(&destfile, outputfile, "wb"); //打开目标文件
	if (tag)
	{
		LOG("open file %s error \n", outputfile);
		exit(EXIT_FAILURE);
	}
	uchar buff[BUFFLEN] = { 0 };
	uchar writebuff[BUFFLEN] = { 0 };
	int wpos = 0;
	int ret = 0;
	uchar tmp = 0;
	uchar bitpos = 0x80;  //1000 0000
	int total = 0;
	rewind(srcfile);

	fwrite(&pfileinfo->total, sizeof(int), 1, destfile);//头部把文件大小填充上
	while ((ret = fread(buff, sizeof(uchar), BUFFLEN, srcfile)) > 0)
	{
		for (int i = 0; i < ret; ++i)
		{
			if (phfc->coding[buff[i]].code == nullptr)
			{
				printf("error %d \n", buff[i]);
				exit(1);
			}
			for (int j = 0; phfc->coding[buff[i]].code[j] != '\0'; ++j)
			{
				tmp |= (phfc->coding[buff[i]].code[j] == '1') ? bitpos : 0;
				bitpos = bitpos >> 1;
				if (bitpos == 0) // 一个字节数据完成
				{
					bitpos = 0x80;
					writebuff[wpos++] = tmp;
					tmp = 0;
					if (wpos == BUFFLEN)
					{
						fwrite(writebuff, sizeof(uchar), wpos, destfile);
						total += wpos;
						wpos = 0;
					}
				}
			}
		}
	}
	if (bitpos != 0)
	{
		writebuff[wpos++] = tmp;
	}
	if (wpos > 0)
	{
		total += wpos;
		fwrite(writebuff, sizeof(uchar), wpos, destfile);
	}
	LOG("压缩文件成功\n");
	fclose(destfile);
	fclose(srcfile);
	destfile = nullptr;
	srcfile = nullptr;

	free(pfileinfo->frepdata);
	pfileinfo->frepdata = nullptr;
	for (int i = 0; i < phfc->chkind; ++i)
	{
		free(phfc->coding[i].code);
		phfc->coding[i].code = nullptr;
	}
	free(phfc->coding);
	phfc->coding = nullptr;
}

解压缩代码:拿到配置文件信息,和压缩文件的头部信息,就开始解压了,构建哈夫曼树,不用构建编码了,遍历压缩文件,根据哈夫曼树结点权值找到对应字符读到缓冲区,写到新文件

void DeCompress(const char* newfilename, const char* compfilename, HuffmanTree* phft)
{
	uchar buff[BUFFLEN] = { 0 };
	uchar writebuff[BUFFLEN] = { 0 };
	int wpos = 0;
	int ret = 0;
	uchar tmp = 0;
	uchar bitpos = 0x80;  //1000 0000
	int total = 0;
	FILE* newfile = nullptr;
	FILE* compfile = nullptr;
	errno_t tag = fopen_s(&newfile, newfilename, "wb");
	if (tag)
	{
		LOG("open file %s fail \n", newfilename);
		exit(EXIT_FAILURE);
	}
	tag = fopen_s(&compfile, compfilename, "rb");
	if (tag)
	{
		LOG("open file %s fail \n", compfilename);
		exit(EXIT_FAILURE);
	}
	fread(&total, sizeof(int), 1, compfile);
	bitpos = 0x80;
	wpos = 0;
	int npos = phft->sum - 2;
	while ((ret = fread(buff, sizeof(uchar), BUFFLEN, compfile)) > 0)
	{
		int i = 0;
		while (i < ret)
		{
			do
			{
				if (phft->node[npos].leftchild == 0 && phft->node[npos].rightchild == 0)
				{
					break;
				}
				npos = (buff[i] & bitpos) ? phft->node[npos].rightchild : phft->node[npos].leftchild;
				bitpos = bitpos >> 1;
			} while (bitpos != 0);
			if (phft->node[npos].leftchild == 0 && phft->node[npos].rightchild == 0)
			{
				writebuff[wpos++] = phft->node[npos].ch;
				npos = phft->sum - 2;
				if (wpos == BUFFLEN)
				{
					fwrite(writebuff, sizeof(uchar), wpos, newfile);
					wpos = 0;
				}
				if (--total == 0) break;
			}
			if (bitpos == 0)
			{
				bitpos = 0x80;
				++i;
			}
		}
	}
	if (wpos > 0)
	{
		fwrite(writebuff, sizeof(uchar), wpos, newfile);
	}
	fclose(newfile);
	fclose(compfile);
	newfile = nullptr;
	compfile = nullptr;
}

测试:

当前路径下复制了一个pp.mp4的视频用来测试:

在配置文件里打印了一下文件里字符对应ASCII和出现的的频度

 进行解压

也是成功压缩解压了,不过对于这种二进制文件,哈夫曼这种效率可以说是微乎其微 

 后面我简单测了一下文本文件,能看到压缩还是有效率的 

代码:Xw-oorik/hafuman

对于特殊情况因为比较简单还没处理,比如要压缩的文件只有一种字符,那么我们压缩解压需要哈夫曼树构建哈夫曼编码就太慢了,特殊情况特殊处理,我们直接把字符的ASCII和字节个数记录,写到文件里直接创建即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/163286.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无线耳机跑步会不会掉、最适合跑步用的耳机排名

现在&#xff0c;喜欢运动的人越来越多了。大家都有体会&#xff0c;多数运动是相对枯燥的&#xff0c;在运动时听听音乐&#xff0c;那是多么惬意的事情啊。为此&#xff0c;体验过多款耳机&#xff0c;但令我很满意的甚少。相信不少喜欢运动的朋友都有着跟我一样的烦恼吧&…

【Java基础知识3】Java注释:单行、多行、文档注释(如何通过 javadoc 命令生成代码文档、如何在IEDA配置自动为所有的类都添加创建者和创建日期)

本文已收录专栏 &#x1f332;《Java进阶之路》&#x1f332; 目录 本文已收录专栏 &#x1f332;《Java进阶之路》&#x1f332; &#x1f350;01、单行注释 &#x1f350;02、多行注释 &#x1f350;03、文档注释 &#x1f350;04、文档注释的注意事项 &#x1f350;05、注释…

ceres学习笔记(二)

继续关于ceres官方doc里教程的学习&#xff0c;对于powells function的学习。 一、powells function 鲍威尔法&#xff0c;严格来说是鲍威尔共轭方向法&#xff0c;是迈克尔J.D.鲍威尔提出的一种求解函数局部最小值的算法。该函数不能是可微分的&#xff0c;并且不会导出衍生函…

spring用注解读取与获取对象

前言 上一篇博客简单的介绍了spring的功能与使用&#xff0c;可以看到我们创建一个对象&#xff0c;就需要在xml中存储一个bean对象&#xff0c;这种操作非常的繁琐&#xff0c;因此spring发明了使用注解来快捷存储bean对象 配置工作 我们在xml文件中写下面的代码片段 <…

基于风光储能和需求响应的微电网日前经济调度(Python代码实现)【0】

目录 0 引言 1 计及风光储能和需求响应的微电网日前经济调度模型 1.1风光储能需求响应都不参与的模型 1.2风光参与的模型 1.3风光和储能参与模型 1.4 风光和需求响应参与模型 1.5 风光储能和需求响应都参与模型 2 需求侧响应评价 2.1 负载率 2.2 可再生能源消纳率 …

Win10PE_V2.0Nvme网络版.iso 支持Nvme硬盘免费下载无需积分

Win10PE_V2.0Nvme网络版.iso 支持Nvme硬盘免费下载无需积分 V1.0版本发布 2022年1月19日 内置常用PE工具&#xff0c;7-Zip、EasyImageX_x64、XorBoot Uefi修复、NT6修复、Ghost、CGI、Google浏览器、PENetwork、RegWorkshop、迅雷迷你版、、BOOTICEx64、windows安装器、XP安…

路径计数2

路径计数2 题目描述 一个NNN \times NNN的网格&#xff0c;你一开始在(1,1)(1,1)(1,1)&#xff0c;即左上角。每次只能移动到下方相邻的格子或者右方相邻的格子&#xff0c;问到达(N,N)(N,N)(N,N)&#xff0c;即右下角有多少种方法。 但是这个问题太简单了&#xff0c;所以现…

MySQL 数据同步 Elasticsearch 的技术方案选型

文章目录1.同步双写2.异步双写3.定时任务4.数据订阅1.同步双写 优点&#xff1a;实现简单缺点&#xff1a; 业务耦合&#xff0c;商品的管理中耦合大量数据同步代码 影响性能&#xff0c;写入两个存储&#xff0c;响应时间变长 不便扩展&#xff1a;搜索可能有一些个性化需求&…

jvm学习的核心(三)---运行时数据区详解(1)

图片等相关信息来源于&#xff1a;尚硅谷宋红康JVM全套教程 1.程序计数器 程序计数器又叫pc寄存器&#xff0c;中文有两个名字 我们可以反编译字节码文件查看方法中操作指令对应的指令地址 javap -v "对应的class文件"为什么要用pc寄存器&#xff0c;pc寄存器有什…

13、Javaweb_Filter登陆验证动态代理过滤敏感词Listener

Filter&#xff1a;过滤器 1. 概念&#xff1a; * 生活中的过滤器&#xff1a;净水器,空气净化器&#xff0c;土匪、 * web中的过滤器&#xff1a;当访问服务器的资源时&#xff0c;过滤器可以将请求拦截下来&#xff0c;完成一些特殊的功能。 * 过滤器的作用&…

深入理解计算机系统_可执行目标文件和可重定位目标文件的3个区别

这篇笔记对比一下可执行目标文件和可执行目标的3个区别。下图分别是可重定位目标文件和可执行目标文件各段结构。 1.1 可执行目标文件和可重定位目标文件的3个区别 区别1&#xff1a;可执行目标文件的rel.text和.rel.data消失了 链接器将.o中.text和.data节整合到一起时&a…

【ROS2入门】理解 ROS 2 Topics 话题

大家好&#xff0c;我是虎哥&#xff0c;从今天开始&#xff0c;我将花一段时间&#xff0c;开始将自己从ROS1切换到ROS2&#xff0c;在上一篇中&#xff0c;我们一起了解ROS 2中节点的功能以及与之交互的工具&#xff0c; 这一篇&#xff0c;我们主要会围绕ROS中另外一个重要的…

RS232 RS485 TO ETH TCP-Modbus 测试

原来modbus 传感器都是有对应的指令码的&#xff0c;不同功能的指令码也不一样&#xff0c;比如测温度和湿度的指令码也是不一样的&#xff1b; 硬件连接如下图 &#xff08;温湿度传感器&#xff0c;板载SHT20&#xff09; ​ 编辑切换为居中 添加图片注释&#xff0c;不超…

华为VRRP、BFD实验配置

目录 VRRP实验配置 BFD实验配置 配置单跳检测 配置多跳检测 配置单臂回声 BFD与路由协议联动配置 BFD与OSPF联动 BFD与ISIS联动 BFD与BGP联动 VRRP实验配置 VRRP配置 AR1配置&#xff08;VRRP缺省优先级100&#xff09; int g0/0/0 ip add 192.168.10.1 24 vrrp vrid …

织音云站长扶持计划:可免费获得CDN或虚拟主机

活动介绍活动详情页&#xff1a;织音云站长扶持计划网站被恶意攻击时是中小站长最脆弱的时候&#xff0c;90%的站长都会动“关站不干了”的心思&#xff0c;夹在中间真的很难搞!因此织音云决定为中小站长提供免费的全球CDN加速服务和提供免费的虚拟主机,免备案&#xff01;只需…

智改数转水循环在线监测系统,提升企业生产安

江苏省政府印发《江苏省制造业智能化改造和数字化转型三年行动计划&#xff08;2022&#xff0d;2024年&#xff09;》&#xff0c;提出通过三年的努力&#xff0c;全省制造业数字化、网络化、智能化水平显著提升&#xff0c;新业态、新模式、新动能显著壮大&#xff0c;制造业…

linux系统中使用QT操作硬件蜂鸣器的方法

大家好&#xff0c;今天主要和大家聊一聊&#xff0c;如何使用QT进行蜂鸣器的控制与实现。 目录 第一&#xff1a;资源基本简介 第二&#xff1a;应用实例的代码实现 第三&#xff1a;源文件“mainwindow.cpp”的具体实现 第四&#xff1a;程序运行效果 第一&#xff1a;资…

自动控制原理课程设计

一、实验目的(1)要求学生根据书上习题的要求&#xff0c;自行设计一校正装置&#xff0c;并用本 实验挂件 构成的模拟系统 进行实验和实际调试、使学生能认识到校正装置在系统中的重要性。(2)掌握工程中常用的 二阶系统 和 三阶系统 的工程设计方法。二、实验所需挂件及附件型 …

Docker 安装mysql主从复制

1、新建主服务器容器实例3307docker run -d -p 3307:3306 -v /mydata/mysql-master/log:/var/log/mysql -v /mydata/mysql-master/data:/var/lib/mysql -v /mydata/mysql-master/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORDroot --name mysql-master mysql:5.72、进入/myd…

Acwing——第二场热身赛

题目链接 AcWing 3547. 特殊数字 AcWing 3548. 双端队列 AcWing 3549. 最长非递减子序列 题目描述 3547.特殊数字 我们规定&#xff0c;对于一个整数 a&#xff0c;如果其各位数字相加之和能够被 4 整除&#xff0c;则称它是一个特殊数字。 现在&#xff0c;给定一个整数 n…