Huffman树压缩和解压文件
Huffman树
Huffman树:以静态三叉链的存储结构建立的二叉树
Huffman树是一个带权路径长度最小的二叉树,又称最优二叉树
Huffman树的构造方法
①将每个结点都看作是一个树;
②选择两个根结点值最小的二叉树,构造一个新的二叉树,直至剩一个树为止。
Huffman树的存储(静态三叉链,n为叶子结点个数)
Huffman编码
前缀编码:对每一个字符规定一个0,1串作为其代码,并要求任一字符的代码都不是其它字符代码的前缀。
哈夫曼编码:依据权值构造哈夫曼树,根据此树得到字符集的二进制前缀编码。哈夫曼编码是一种不等长的二进制编码。其特点是字符的编码长度和字符的使用频率成反比。
如:
Huffman树实例
①建立存储结点
class HuffmanNode{//权重public int weight;//父结点public int pNode;//左孩子public int lChild;//右孩子public int rChild;public HuffmanNode() {this.weight = 0;this.pNode = this.lChild = this.rChild = -1;}
}
②预先定义
//ASCII码共256种字符private final int MAX_NUMBER = 256;//静态二叉树private HuffmanNode[] huffMan;//权重private int[] weight;//存储Huffman编码private String[] hfCode;
//初始化public HuffmanTree(){this.huffMan = new HuffmanNode[2*MAX_NUMBER - 1];this.weight = new int[MAX_NUMBER];this.hfCode = new String[MAX_NUMBER];}
③读入文件,计算权值
此处根据ASCII码来对权值进行计算,其静态二叉链长度为256*2-1
/*** 根据文件路径读入文件计算出权重信息* @param file*/public void file2Weight(String infile) {try {BufferedReader bReader = new BufferedReader(new FileReader(infile));int c; //以ASCII码的形式接收字符while((c = bReader.read()) != -1) {weight[c]++;} bReader.close();} catch (Exception e) {e.printStackTrace();} }
④根据权值构建Huffman树
这一步的重点就是在已有的树中找到两个权值最小的结点
/*** 构建哈夫曼树*/public void createHuffmanTree() {int index = 0;//将权重存入Huffman树for(; index< this.MAX_NUMBER; index++) {huffMan[index] = new HuffmanNode();huffMan[index].weight = weight[index];}//对Huffman树进行更新for(;index < 2*this.MAX_NUMBER-1; index++) {huffMan[index] = new HuffmanNode();//右子树等于左子树int rchild = getMinWeight(index);int lchild = getMinWeight(index);//设置父结点huffMan[rchild].pNode = index;huffMan[lchild].pNode = index;//创造父结点huffMan[index].weight = huffMan[rchild].weight + huffMan[lchild].weight;huffMan[index].lChild = lchild;huffMan[index].rChild = rchild;}}
从表中取出最小结点:
/*** 在已经存在的Huffman结点中找到最小的* @return 返回权重最小的结点*/private int getMinWeight(int stop) {int move = 0;int minWeight = 99999; //赋予较大的值int min = 0; //记录最小的点while(move < stop) {if(huffMan[move].pNode == -1) {if(huffMan[move].weight < minWeight) {minWeight = huffMan[move].weight;min = move;} } move++;} //进行标记huffMan[min].pNode = -2; return min;}
⑤根据Huffman树得出Huffman编码
借助栈的特性,从叶子结点回溯到根结点,对回溯路径进行存储,再输出即编码
/*** 根据HuffMan树提取HuffMan码* 从数据端开始,向根回溯,用栈进行存储*/public void createHuffmanCode() {int satck[] = new int[this.MAX_NUMBER];int top = 0;int move,parent;for(int i=0; i < this.MAX_NUMBER; i++) {move = i;//之前我设置的默认父结点为-1,此处可用于判断while((parent = huffMan[move].pNode) != -1) {//左孩子为零,右孩子为一if(huffMan[parent].lChild == move) {satck[top++] = 0;}else {satck[top++] = 1;}move = parent; }//出栈,拼接(编码)StringBuilder sBuilder = new StringBuilder();for(int j = top-1; j >= 0; j--) {sBuilder.append(satck[j]);}top = 0;this.hfCode[i] = sBuilder.toString();}}
⑥再次遍历文件,将对应字符转为Huffman编码
在此处有一个问题:因为Huffman码是以String数组存储的,即每一位都是一个字符,占1个字节,8位的空间;而在转换过程中每一个字符都会变成一串编码,于是文件在压缩后并没有变小,反而变大了。
所以,我们要将每一位都变成一个字节类型,然后进行存储。
但是,java中并无直接以字节进行传输的IO流、于是乎,需将每8位字节转为一个byte类型,然后通过字节流的byte数组进行写入。此时要注意的是,字符串最后可能未满8位需在低位补1(防止100等直接转为0,导致数据丢失)。
/*** 对文件按照huffman编码进行编码,并写入文件* @param infile 源文件* @return 压缩文件地址*/public String condenseFile(String infile) {String newFilename = null;try {BufferedReader bReader = new BufferedReader(new FileReader(infile));int c;StringBuilder sBuilder = new StringBuilder();while((c = bReader.read()) != -1) {sBuilder.append(hfCode[c]);}byte[] data = new byte[sBuilder.toString().length()/8+1];int exceed = str2ByteArray(sBuilder.toString(), data);newFilename = infile.substring(0,infile.indexOf("."))+"press"+exceed+infile.substring(infile.indexOf("."));BufferedOutputStream boStream = new BufferedOutputStream(new FileOutputStream(newFilename));boStream.write(data); boStream.flush();bReader.close();boStream.close();} catch (Exception e) {e.printStackTrace();} return newFilename;}
数据转换:此时还需注意byte是有符号类型,最高位为符号位,当数据末尾恰好满8位也应进行判断,否则会造成数组越界。
/*** 将字符串转为byte数组,并返回填充长度* @param str 字符串* @param data byte数组* @return 填充长度*/private int str2ByteArray(String str,byte data[]) {//计算数组长度int result = 0;int cursor = 0; //设置游标char[] ch = new char[8];//每八位拼出一个,有符号byte,对于最后,若不足八位,后面添1for(int i=0; i < data.length-1; i++) {ch = str.substring(cursor, cursor+8).toCharArray();for(int j=1; j < ch.length; j++) {result += java.lang.Math.pow(2, 8-1-j)*(ch[j]-48);}//判断符号位if(ch[0]-48 == 1) {result = 0 - result;}data[i] = (byte)result;//移动游标,结果置零cursor = cursor + 8;result = 0;}//对于最后,若不足八位,后面添1(防止10变为0)if(cursor == str.length()) { //若恰好够八位data[data.length-1] = 0;return 8; }else {ch = str.substring(cursor).toCharArray();for(int j=1; j < str.length()-cursor; j++) {result += java.lang.Math.pow(2, 8-1-j)*(ch[j]-48);}for(int k=str.length()-cursor; k<8; k++) {result += java.lang.Math.pow(2, 8-1-k);}if(ch[0]-48 == 1) { result = 0 - result;}data[data.length-1] = (byte)result;return 8-(str.length()-cursor);} }
PS:在该处我使用了一个取巧的办法,将最后填补的位数放在了文件名中
⑦遍历压缩文件,根据Huffman树解码
解码方法:读文件会得到一个byte类型数据,然后转化为8位,取一位跟Huffman树做比对,0向左孩子走,1向右孩子走,到叶子结点就输出。
此处对文件名中的信息进行提取。
/*** 对压缩后的文件进行解压* @param unPressFile 解压目的地址*/public void recoverFile(String pressFile, String unPressFile) {try {BufferedWriter bWriter = new BufferedWriter(new FileWriter(unPressFile));BufferedInputStream biStream = new BufferedInputStream(new FileInputStream(pressFile));int size = Integer.parseInt(pressFile.substring(pressFile.indexOf("press")+5, pressFile.indexOf("press")+6));int c,judge;int move = 2*this.MAX_NUMBER - 2; //从哈夫曼树的最顶端开始查询c = biStream.read();int length = 8; //数组中存储的实际有用数据while(c != -1) {judge = biStream.read();//判断是否为结尾if(judge == -1) {length = length - size;}c = (byte)c;int[] data= byte2Int(c);//需先走树再判断,不然最后一位取不到for (int i = 0; i < length; i++) {//0走左树,1走右树if(data[i] == 0) {move = huffMan[move].lChild;}else {move = huffMan[move].rChild;}if(huffMan[move].lChild == -1 && huffMan[move].rChild == -1 ) {bWriter.write((char)move);bWriter.flush();move = 2*this.MAX_NUMBER - 2; //重置 }}c = judge;}biStream.close();bWriter.close();} catch (Exception e) {e.printStackTrace();}}
解压文件时数据转换:即将byte类型转换为01码```
/*** 将一个byte长的数转为二进制* @param num byte数* @return int数组*/public int[] byte2Int(int num) {//返回结果int[] result = {0,0,0,0,0,0,0,0};//栈int[] stack = {-1,-1,-1,-1,-1,-1,-1,-1};int top = 0;//有符号if(num < 0) {result[0] = 1;num = 0 - num;}//入栈while(num != 0) {stack[top++] = num%2;num = num/2;}//将栈中数据存入结果数组int move = 7; //结果数组游标for(int i=0; i < top; i++) {result[move--] = stack[i];} return result;}
⑧主函数测试
HuffmanTree hfTree = new HuffmanTree();String inFile = "Test/Hufin.txt";String pressFile;String unPressFile= "Test/Huffmanunpress.txt";//①建立权值表hfTree.file2Weight(inFile);//②建立Huffman树hfTree.createHuffmanTree();//③求得huffman编码hfTree.createHuffmanCode(); //④根据编码压缩文件pressFile = hfTree.condenseFile(inFile);System.out.println("压缩后地址为:"+pressFile);//⑤根据编码解压文件hfTree.recoverFile(pressFile,unPressFile);
测试情况:
控制台输出:
文件内容比对:
文件大小比对:
Huffman树压缩和解压文件相关推荐
- Qt qCompress和qUncompress 压缩和解压文件
利用Qt的qCompress和qUncompress来压缩和解压文件 有个特点,用qCompress压缩的文件不能直接用别的软件来解压,需要经过处理,否则只能利用Qt的qUncompress来解压,因 ...
- JAVA 7z Seven Zip 压缩和解压文件
JAVA 7z Seven Zip 压缩和解压文件 7-Zip是基于GNU LGPL协议发布的软件,通过全新算法使压缩比率大幅提升 本文主要讲解通过JAVA方式把文件压缩成7z文件和对7z文件进行解压 ...
- mac 命令行 解压7z文件_如何在Mac上快速压缩和解压文件?Mac上解压和压缩文件的方法...
苹果mac电脑怎么压缩和解压文件?Mac电脑仅默认支持把文件压缩成zip格式,解压成zip.tar.gz,bz2等格式,有些操作需要安装第三方软件来完成,这篇文章为大家带来几种关于在Mac上解压和压缩 ...
- Linux系统-gzip命令 – 压缩和解压文件
gzip命令来自于英文单词gunzip的缩写,其功能是用于压缩和解压文件.gzip是一款使用广泛的压缩工具,文件经过压缩后一般会以.gz后缀结尾,与tar命令合用后即为.tar.gz后缀. 据统计,g ...
- 总结Linux系统压缩和解压文件指令——gzip/gunzip 指令、zip/unzip 指令、tar 指令
Linux系统压缩和解压文件指令 gzip/gunzip 指令:gzip 用于压缩文件, gunzip 用于解压的 基本语法 应用实例 细节说明 zip/unzip 指令:zip 用于压缩文件, un ...
- WinRAR 分卷压缩和解压文件
WinRAR 分卷压缩和解压文件 1. WinRAR http://www.winrar.com.cn/ 1.1 分卷压缩文件 文件 -> 添加到压缩文件 -> 切分为分卷 (V),大小 ...
- 如何在Mac上快速压缩和解压文件?Mac上解压和压缩文件的方法
苹果mac电脑怎么压缩和解压文件?Mac电脑仅默认支持把文件压缩成zip格式,解压成zip.tar.gz,bz2等格式,有些操作需要安装第三方软件来完成,这篇文章为大家带来几种关于在Mac上解压和压缩 ...
- 用SharpZipLib来压缩和解压文件 --zt
from:http://www.cnblogs.com/zhangweiguo3984/archive/2007/03/15/314333.html#675634 1.建立工程,添加引用,添加Shar ...
- linux bzip2压缩文件,bzip2命令_Linux bzip2命令:压缩和解压文件(.bz2文件)
有时候你会发现并不是所有的 Linux 压缩包都是以 .tar.gz 为后缀的,有些压缩包的后缀是 .tar.bz2.这个 .tar.bz2 又是什么呢?它就是本文的主角:bzip2 压缩工具. 有了 ...
最新文章
- amd同步多线程_AMD下一代锐龙APU实锤!Zen3、RDNA2绝配
- html自定义标签提示,用简单的jquery+CSS创建自定义的a标签title提示tooltip_HTML/Xhtml_网页制作...
- 区块链BaaS云服务(7)微软Azure区块链服务
- 按某列获取几行_机器学习获取数据难?别忘记特征工程
- 即插即用的轻量注意力机制ECA--Net
- centos 8 rpm yum install_关于yum不能正常使用的解决方案
- Microsoft Access、MySQL 以及 SQL Server 所使用的数据类型和范围。
- One question regarding your note Note 1731777 - Debugging background work items
- andpods授权码订单号分享_微信OAuth2授权登录
- Java 8 函数式编程学习笔记
- 实用工具推荐:LICEcap(屏幕录制.gif)
- 关于SDWebImage
- 100行JS代码实现❤坦克大战js小游戏源码 HTML5坦克大战游戏代码(HTML+CSS+JavaScript )...
- 站长必会数据统计工具教程:百度统计 VS GA
- 软件测评师的一些重点①
- AI后门检测论文翻译:Universal Litmus Patterns: Revealing Backdoor Attacks in CNNs
- VirtualBox AndroidX86 网络设置
- C# Dictionary多线程安全访问问题
- oracle查运行sql语句,查询Oracle正在执行的SQL语句
- cocos Creator 3.2 关于 NodePool 对象池的应用- (弹出框)
热门文章
- BGP——权重选路(讲解+配置命令)
- 【问题记录】 Linux分区磁盘占满,导致ssh登陆闪退
- 从厕所排队引发的产品设计方案思考
- 深入Redis客户端(redis客户端属性、redis缓冲区、关闭redis客户端)
- java 课后习题 温度转换
- 微信小程序request:fail invalid url
- 【Linux】在Deepin v20或UOS20下运行MC我的世界
- C#LeetCode刷题之#811-子域名访问计数​​​​​​​(Subdomain Visit Count)
- Redux中的功能式React式编程简介
- 编写react组件_如何编写第一个React.js组件