这个程序是研一上学期的课程大作业。当时,跨专业的我只有一点 C 语言和数据结构基础,为此,我查阅了不少资料,再加上自己的思考和分析,实现后不断调试、测试和完善,耗时一周左右,在 2012/11/19 完成。虽然这是一个很小的程序,但却是我完成的第一个程序。

源码托管在 Github:点此打开链接

以下为完整的作业报告:

一、问题描述

名称:基于哈夫曼编码的文件压缩解压

目的:利用哈夫曼编码压缩存储文件,节省空间

输入:任何格式的文件(压缩)或压缩文件(解压)

输出:压缩文件或解压后的原文件

功能:利用哈夫曼编码压缩解压文件

性能:快速

二、问题的初步讨论

为了建立哈夫曼树,首先扫描源文件,统计每类字符出现的频度(出现的次数),然后根据字符频度建立哈夫曼树,接着根据哈夫曼树生成哈夫曼编码。再次扫描文件,每次读取8bits,根据“字符—编码”表,匹配编码,并将编码存入压缩文件,同时存入编码表。解压时,读取编码表,然后读取编码匹配编码表找到对应字符,存入文件,完成解压。

三、总的UML协同图

四、文件读取方式和处理单元的分析

压缩解压的第一步就是读取文件,为了能够处理任何格式的文件,采用二进制方式读写文件。以一个无符号字符(unsigned char)的长度8位为处理单元,最多有256(0~255)种组合,即256类字符。

五、字符频度扫描的分析

要建立哈夫曼树,先要得到各类字符的频度,我想到了两种扫描方案:

1、利用链表存储,每扫描到一类新字符就动态分配内存;

2、利用数组,静态分配256个空间,对应256类字符,然后用下标随机存储。

链表在需要时才分配存储空间,可以节省内存,但是每加入一个新字符都要扫描一次链表,很费时;考虑到仅有256个字符种类,不是很多,使用静态数组,不会造成很大的空间浪费,而可以用数组的下标匹配字符,不需扫描数组就可以找到每类字符的位置,达到随机存储的目的,效率有很大的提高。当然,不一定每类字符都出现,所以,统计完后,需要排序,将字符频度为零的结点剔除。

我定义的数组类似这样:Node array[CHAR_KINDS],其中CHAR_KINDS为8位无符号字符对应的256(0~255)种不同组合,这样每扫描到一个字符,直接将字符作为下标,就可以找到字符的位置。

六、建立哈夫曼树的分析

哈夫曼树为二叉树,树结点含有权重(在这里为字符频度,同时也要把频度相关联的字符保存在结点中)、左右孩子、双亲等信息。

考虑到建立哈夫曼树所需结点会比较多,也比较大,如果静态分配,会浪费很大空间,故我们打算用动态分配的方法,并且,为了利用数组的随机访问特性,也将所需的所有树节点一次性动态分配,保证其内存的连续性。另外,结点中存储编码的域,由于长度不定,也动态分配内存。

6.1、这时,针对上面的字符扫描结点就要做一些改动

将其定义成临时结点TmpNode,这个结点仅保存字符及对应频度,也用动态分配,但是一次性分配256个空间,统计并将信息转移到树结点后,就将这256个空间释放,既利用了数组的随机访问,也避免了空间的浪费。

七、生成哈夫曼编码的分析

每类字符对应一串编码,故从叶子结点(字符所在结点)由下往上生成每类字符对应的编码,左‘0’,右‘1’。为了得到正向的编码,设置一个编码缓存数组,从后往前保存,然后从前往后拷贝到叶子结点对应编码域中,根据上面“建立哈夫曼树的协商”的约定,需要根据得到的编码长度为编码域分配空间。对于缓存数组的大小,由于字符种类最多为256种,构建的哈夫曼树最多有256个叶子结点,树的深度最大为255,故编码最长为255,所以分配256个空间,最后一位用于保存结束标志。

八、文件压缩的分析

上面协定以8位的字符为单元编码,这里压缩当然也以8位为处理单元。

首先将字符及种类和编码(编码表)存储于压缩文件中,供解压时使用。

然后以二进制打开源文件,每次读取一个8位的无符号字符,循环扫描匹配存储于哈夫曼树节点中的编码信息。

由于编码长度不定,故需要一个编码缓存,待编码满足8位时才写入,文件结束时缓存中可能不足8位,在后面补0,凑足8位写入,并将编码的长度随后存入文件。

在哈夫曼树节点中,编码的每一位都是以字符形式保存的,占用空间很大,不可以直接写入压缩文件,故需要转为二进制形式写入;至于如何实现,可以定义一个函数,将保存编码的字符数组转为二进制,但是比较麻烦,效率也不高;正好,可以利用C语言提供的位操作(与、或、移位)来实现,每匹配一位,用“或”操作存入低位,并左移一位,为下一位腾出空间,依次循环,满足8位就写入一次。

8.1、压缩文件的存储结构

结构说明:字符种类用来判断读取的字符、频度序偶的个数,同时用来计算哈夫曼结点的个数;文件长度用来控制解码生成的字符个数,即判断解码结束。

九、文件解压的分析

以二进制方式打开压缩文件,首先将文件前端的字符种类数读取出来,据此动态分配足够空间,然后将随后的字符—编码表读取处理保存到动态分配的结点中,然后以8位为处理单元,依次读取随后的编码匹配对应的字符,这里对比编码依然用在文件压缩中所用的方法,就是用C语言的位操作,同0x80与操作,判断8bits字符的最高位是否为‘1’,对比一位后,左移一位,将最高位移除,次高位移到最高位,依次对比。这次是从编码到字符反向匹配,与压缩时有一点不同,需要用读取的编码逐位与编码表中的编码进行对比,对比一位后,增加一位再对比,而且每次对比都是一个循环(与每个字符的编码对比),效率很低。

于是,我思考另外的方法,可以将哈夫曼树保存到文件中,解码时,从树根到叶子对比编码,只要一次遍历就可以找到编码对应的存于叶子结点中的字符,极大提高了效率。

然而,我们发现树结点中有字符、编码、左右孩子、双亲,而且孩子和双亲还必须是整型的(树节点最多为256*2-1=511个),占用空间很大,会导致压缩文件变大,这是不可取的,因为我们的目的就是压缩文件。

我们进一步考虑,可以仅存储字符及对应频度(频度为unsigned long,一般情况下与int占用空间一样,同为4个字节),解码时读取数据重建哈夫曼树,这样就解决了空间问题。

虽然重建哈夫曼树(双重循环,每个循环的次数最大为511)也要花费一定的时间,但是相对上面的与编码表匹配(每位编码都要循环匹配所有字符(最多为256种)一次,而总的编码位数一般很大,且随着文件变大而增长)所花费的时间更少。

9.1由于解压的方式变了,在这里要对上面的协商作一些修改

1、修改后的总UML协同图:

2、在文件压缩时,就不需要保存编码表,改为保存字符及对应权重。

3、在文件压缩时,处理最后不足8位的编码后,不再需要保存编码的长度,因为解压时从树根向下匹配,到达叶子就停止(所有叶子结点都在连续分配的树结点空间的低端,故可以用结点下标判断是否到达叶子结点),不会超过而读取最后的无效编码。

十、定义所需类:

10.1、文件压缩所需类

行为说明:char_kinds保存出现的字符种类;char_temp用来保暂存字符;code_buf暂存匹配出来的编码;compres()是主压缩函数,接收两个文件名,一个输入,可以是任何格式的待压缩文件,一个输出,为压缩后的编码文件;

10.2、文件解压所需类

行为说明:char_kinds保存出现的字符种类;char_temp用来保暂存字符;root保存解码时的当前结点索引,用来判断是否达到叶子结点;extract()是主压缩函数,接收两个文件名,一个输入,为压缩后的编码文件,一个输出,为解码后的原文件;

10.3、其他重要类

行为说明:

1、tmp_nodes用来保存字符频度,动态一次性分配256个空间,统计后删除;CalChar()用于生成8位的256个字符及对应频度(出现次数);

2、node_num保存树结点总数,CreateTree()建立哈夫曼树,select()函数用来找最小的两个结点;

3、huf_node树结点用来保存编码信息,HufCode()生成哈夫曼编码;

10.4、类的关联图

行为说明:CreateTree()和HufCode()供compress调用,前者建立哈夫曼树,后者生成哈夫曼编码;CreateTree()供extrac()调用,重建哈夫曼树,用于解码;

十一、编码行为状态图

后来我在初步编码时,发现一些问题:解码后无法得到完全正确的源文件,经过排查,发现以EOF判断压缩文件的结束不可取,因为压缩文件是二进制文件,而EOF一般用来判断非二进制文件的结束,所以我们加入了文件长度来控制。

11.1、于是上面的协商需要一些改动

1、修改后的字符统计类:

2、修改后的文件压缩类:

3修改后的编码行为状态图:

十二、函数实现:

12.1、实现语言及编码环境:

实现语言:C语言,兼容嵌入式,运行效率高

编码环境:XP+VS2010(debug模式)

12.2、结构体及函数定义

两个重要的结点结构体:

三个函数用于建立哈夫曼树和生成哈夫曼编码:

两个主要函数——压缩解压函数:

12.3、函数说明

12.3.1、其他函数:

Select函数供CreateTree函数调用,找两个最小的结点,找到第一个后需要将其parent设为‘1’(初始化后为‘0’)表明此结点已被选中:

建立哈夫曼树,每次用select()函数找两个最小结点:

生成哈夫曼编码,由叶子到根反向生成编码,左‘0’,右‘1’,每个code域的内存动态分配:

12.3.2、压缩函数中的几个部分的说明

动态分配256个暂存结点,用下标索引统计字符频度:

这里以feof来判断文件结束,是由于eof判断的文件类型比较局限,而feof在读完最后一个字节之后,再次读文件时才会设置结束标志,所以需要在while循环之前读一次,然后每次在循环的最后读取文件,这样可以正确判断文件结束;以位操作来匹配编码,每次存入最低位,然后左移一位,依次循环处理,满8位保存一次:

最后缓冲中不足8位,补0凑足8位(左移):

12.3.3、解压函数中的说明

压缩文件为二进制文件,feof在这里无法正确判断结束,故用一个死循环处理编码,以压缩时存储的文件长度来控制循环的结束。每当root小于char_kinds,就匹配到了一个字符,是因为字符的下标范围是0~char_kinds-1。

十三、程序健壮性考虑

13.1、字符种类为‘1’:

当字符种类为‘1’时,只有一个哈夫曼结点,无法构造哈夫曼编码,但是可以直接处理,依次保存字符种类数、字符、字符频度(此时就是文件长度)即可,解压时仍然先读取字符种类数,为‘1’则特殊处理,读取字符和频度(此时就是文件长度),利用频度控制循环,输出字符到文件即可,此时压缩文件的存储结构为:

在压缩函数前部加入特殊情况的判断和处理:

在解压函数前部加入特殊情况判断和处理:

13.2、输入文件不存在:

由于压缩或解压时,输入文件必须是存在的,而用户可能会输错,因此有必要加入输入文件的存在性进行判断,防止文件不存在而导致程序异常退出:

1、将压缩解压的返回值改为int:

2、在压缩和解压函数中加入:

3、在main函数中加入压缩解压函数是否异常退出的判断:

十四、系统测试:

14.1、测试流程图:

14.2、代码运行测试

14.2.1、使用说明:

(编译链接生成的可执行文件为:hufzip.exe)

双击hufzip.exe运行,输入所选择操作类型的数字代号:

1:compress(压缩)

2:extract(解压)

3:quit(退出)

然后提示输入源文件和目标文件,可以输入完整的路径名加文件名,也可以仅输入一个文件名(默认在当前运行目录下寻找),如果不小心输错源文件名或源文件不存在,将提示出错,然后可以再次输入,如下图所示:

14.2.2、测试的几个文件

“1.txt”中为全为字符‘a’(共1024*1024个),由于只有一种字符,压缩文件只保存了字符种类(4byte)、字符(1byte)和字符频度(4byte),故为9字节,控制台及文件压缩情况如下(1.txt.hufzip为压缩文件,1.hufzip.txt为解压后的文件):

 

“2.txt”为0~255的整数,其中0出现1次,1出现2次,……,255出现256次,其控制台及压缩情况如下(2.txt.hufzip为压缩文件,2.hufzip.txt为解压后的文件):

 

“3.doc”为一个随意的word文档,其控制台及压缩情况如下(3.doc.hufzip为压缩文件,3.hufzip.doc为解压后的文件):

 

“4.jpg”是一个图像文件,输入绝对路径对4.jpg进行压缩,其控制台及压缩情况如下(4.jpg.hufzip为压缩文件,4.hufzip.jpg为解压后的文件):

 

14.2.3、由上面的几种文件的压缩前后的对比可以得出

哈夫曼编码对文本文件,一般可以达到大约2:1的压缩比,特别是有规律的文本文件,可以达到高于2:1的压缩比,而对于图像等特殊文件压缩比几乎为1:1,效果不理想。

十五、总结:

通过这一个压缩和解压程序的设计,我学习了UML的使用,提升了编码能力,提升了调试能力,总之,受益匪浅。

转载自:http://www.cnblogs.com/keke2014/p/3857335.html

基于哈夫曼编码的文件压缩解压相关推荐

  1. 基于哈夫曼编码对文件进行压缩和解压缩(详细讲解)

    基于哈夫曼编码对文件进行压缩和解压缩(详细讲解) 本文对应c++代码实现链接 一.背景 利用特定的算法来压缩数据的工具,压缩后生成的文件称为压缩包.如果想使用其中的数据,就得用压缩软件对数据进行解压. ...

  2. 基于哈夫曼算法的文件压缩软件

    数据结构课设(一) 作业要求 1.设计并实现一个使用哈夫曼算法对文件进行压缩的工具软件. 2.通过命令行参数指定操作模式(压缩/解压).源文件名.目标文件名. 3.压缩操作将源文件按字节读入并统计字节 ...

  3. 哈夫曼编码与文件压缩

    一:哈夫曼树与哈夫曼编码 大家知道,文件压缩的原理么? 假如我们有一个文件,文件当中仅有 A.B.C.D.E 五种字符,这五种字符出现的频率分别为 5次.4次.3次.2次.1次. 我们知道每个英文字母 ...

  4. 使用哈夫曼编码实现文件压缩__win10,c++

    系统:win10 工具:vc6.0 //我加了个计时,用int存储字符总数 //因此增加一个限制:文件不可大于2GB #include<iostream> #include<time ...

  5. 使用哈夫曼编码进行文件压缩

    给你一个图片文件,要求对其进行无损压缩, 看看压缩效果如何. 思路:读取文件-> 得到赫夫曼编码表 -> 完成压缩 将前面压缩的文件,重新恢复成原来的文件. 思路:读取压缩文件(数据和赫夫 ...

  6. 基于哈夫曼编码完成的文件压缩及解压

    这几天在较为认真的研究基于哈夫曼编码的文件压缩及解压,费了点时间,在这分享一下: 这里用链式结构,非顺序表结构: 文件压缩: 1.获取文件信息(这里采用TXT格式文本): 2.压缩文件: 3.写配置文 ...

  7. 哈夫曼编码解压缩文件 - Java实现

    文章目录 前言 一.文件压缩 二.文件解压 结语 前言 不了解哈夫曼树的可以移步查看我的另一篇博客:哈夫曼树(最优二叉树) 使用哈夫曼编码压缩文件,其实就是将每个字符的哈夫曼编码得到,每8位转为一个字 ...

  8. 哈夫曼树实现文件压缩

    最近在学了哈夫曼树之后,作为练习,写了一个文件压缩的小项目: 在这里和大家分享一下: 主要实现思路: 利用哈夫曼树的特性对字符进行哈夫曼编码,其中运用到了最小堆:利用最小堆的特性,找出构造哈夫曼树的结 ...

  9. Qt实现哈夫曼编码解压缩软件详解

    目录 一.概要设计 二:设计效果展示: 三.源代码 1°MainWindow.h 2°MainWindow.cpp 3°Compression.h 4°Compression.cpp 四.软件分析 诸 ...

  10. Qt基于QuaZIP实现文件压缩/解压(Win下)

    Qt基于QuaZIP实现文件压缩/解压(Win下) 一.工具准备 二.编译zlib 1.下载zlib源码 2.生成VS工程文件 3.使用VS进行编译 三.编译QuaZIP 1.下载QuaZIP源码 2 ...

最新文章

  1. buu 密码学的心声
  2. Directory Service Maintenance
  3. kubeadm安装K8S单master双节点集群
  4. 鸿蒙系统桌面小艺建议怎么删除?鸿蒙系统小艺建议怎么设置关闭及开启
  5. .Net ViewState的实现(转载)
  6. 全国第一家FPGA云主机(FAAS)正式启动售卖,被阿里云抢先了。
  7. oracle not aviable,ORA-01034: ORACLE not available
  8. 代码整洁之道读书笔记----第三章---函数--第一节-专注且短小
  9. java中国象棋编程思想_中国象棋网络对战平台系统.doc
  10. 2019裁员潮,测试被裁了能干什么?
  11. Python将Word转换为Pdf格式文件(包含批量转换)
  12. Windows 10 的触摸板手势
  13. 如何使用百度统计监测网站流量
  14. php的vget方法,ext_lanzou.php
  15. EWAHCompressedBitmap数据结构及原理
  16. sql2012试用版本过期处理
  17. Object Detection in 20 Years A Survey 论文阅读笔记
  18. APP在电脑模拟器上完美运行,真机运行时闪退现象记录
  19. Evita Full-Medium-Light与SHE差异
  20. 论文,更不能培养自己研究问题的能力。

热门文章

  1. 机动车尾气污染物分析仪的原理
  2. realme 制作一键root
  3. 微信 PC(电脑端) 多开批处理
  4. 美国公布自动驾驶新政AV4.0;微软Access数据库出现漏洞,或致8.5万家企业面临风险;苹果谈论隐私问题……...
  5. 开源接口测试平台BTest
  6. java基础中的基础,简单中的简单
  7. 如何退出或卸载奇安信天擎软件
  8. Linux电源管理_Wakeup events framework--(二)
  9. MySQL常见运算符详解
  10. 面对失败计算机科学家,面对巨大挫折,仍毫不畏惧、坚韧不拔的名人事例