[转载]java代码采用哈夫曼实现压缩软件
说到文件压缩大家很容易想到的就是 rar,zip 等我们常见的压缩格式。然而,还有一种就是大家在学习数据结构最常见到的哈夫曼树的数据结构,以前还不知道他又什么用,其实他最大的用途就是用来做压缩,也是一些 rar,zip 压缩的祖先,称为哈弗曼压缩(什么你不知道谁是哈弗曼,也不知道哈弗曼压缩,不急等下介绍)。
特别是声音、图像和视频等媒体在人们的日常生活和工作中的地位日益突出,这个问题越发显得严重和迫切。如今,数据压缩技术早已是多媒体领域中的关键技术之一。
一、什么是哈弗曼压缩
Huffman( 哈夫曼 ) 算法在上世纪五十年代初提出来了,它是一种无损压缩方法,在压缩过程中不会丢失信息熵,而且可以证明 Huffman 算法在无损压缩算法中是最优的。 Huffman 原理简单,实现起来也不困难,在现在的主流压缩软件得到了广泛的应用。对应用程序、重要资料等绝对不允许信息丢失的压缩场合, Huffman 算法是非常好的选择。
二、怎么实现哈弗曼压缩
2 、哈夫曼编码 (Huffman Coding) :是一种编码方式,哈夫曼编码是可变字长编码 (VLC) 的一种。 uffman 于 1952 年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长 度最短的码字,有时称之为最佳编码,一般就叫作 Huffman 编码。
三、哈夫曼编码生成步骤:
① 扫描要压缩的文件,对字符出现的频率进行计算。
② 把字符按出现的频率进行排序,组成一个队列。
③ 把出现频率最低(权值)的两个字符作为叶子节点,它们的权值之和为根节点组成一棵树。
④ 把上面叶子节点的两个字符从队列中移除,并把它们组成的根节点加入到队列。
⑤ 把队列重新进行排序。重复步骤 ③④⑤ 直到队列中只有一个节点为止。
⑥ 把这棵树上的根节点定义为 0 (可自行定义 0 或 1 )左边为 0 ,右边为 1 。这样就可以得到每个叶子节点的哈夫曼编码了。
既如
如果假设树的左边用0 表示右边用 1 表示,则每一个数可以用一个 01 串表示出来。
则可以得到对应的编码如下:
1-->110
2-->111
3-->10
4-->0
每一个01 串,既为每一个数字的哈弗曼编码。
为什么能压缩:
压缩的时候当我们遇到了文本中的1 、 2 、 3 、 4 几个字符的时候,我们不用原来的存储,而是转化为用它们的 01 串来存储不久是能减小了空间占用了吗。(什么 01 串不是比原来的字符还多了吗?怎么减少?)大家应该知道的,计算机中我们存储一个 int 型数据的时候一般式占用了 2^32-1 个 01 位,因为计算机中所有的数据都是最后转化为二进制位去存储的。所以,想想我们的编码不就是只含有 0 和 1 嘛,因此我们就直接将编码按照计算机的存储规则用位的方法写入进去就能实现压缩了。
比如:
1这个数字,用整数写进计算机硬盘去存储,占用了 2^32-1 个二进制位
效果显而易见。
开始写程序:
开始些程序之前,必须想好自己的文件存储格式,和存储规则是什么
为了简便,我自定义存储的基本信息,格式如下:
SaveCode[i].n
B
yte[]
有了定义好的格式我们的读写就非常的方便了,
创建步骤:
①读入文件中的所有字节:
- //
创建文件输入流 -
java.io.FileInputStream fis = java.io.FileInputStream(path); -
-
-
i=fis.read(); -
byteCount[i]++; -
}
②构建哈夫曼树:
-
-
void createTree(){ -
-
PriorityQueue<hfmNode> nodeQueue = PriorityQueue<hfmNode>(); -
-
队列里面去 -
( inti= 0;i<256;i++){ -
-
hfmNode node = hfmNode(i,byteCount[i]); -
nodeQueue.add(node); -
} -
} -
-
-
{ -
hfmNode min1 = nodeQueue.poll(); -
hfmNode min2 = nodeQueue.poll(); -
-
hfmNode result = hfmNode( 0,min1.times+min2.times); -
result.lChild=min1; -
result.rChild=min2; -
nodeQueue.add(result); -
} -
root=nodeQueue.peek(); - }
这里我采用了优先队列的方法,因为优先队列比较符合哈夫曼的构造方法,形式上也非常的相似。
③取得每一个叶子节点的哈夫曼编码:
-
-
void getMB(hfmNode root,String s){ -
((root.lChild== null)&&(root.rChild==null)){ -
Code hc= Code(); -
hc.node=s; -
hc.n=s.length(); -
System.out.println( -
SaveCode[root.data]=hc; root.data 的编码 HC -
} -
(root.lChild!= null){//左0右 1 -
getMB(root.lChild,s+ -
} -
(root.rChild!= null){ -
getMB(root.rChild,s+ -
} -
}
如此一来我们的哈夫曼树就建好了。
下面就是按照我们之前定义的文件存储格式直接写进文件就可以了。
压缩文件的写入:
①
② 写出每一个字节所对应的编码
这一步较为复杂,因为JAVA
中没有自己定义的二进制为写入,我们就不得不将每 8 个 01
- /////写入每一个字节所对应的
String编码 -
count= 0;//记录中转的字符个数 -
i= 0;//第i个字节 -
String writes = -
String tranString= -
String waiteString; -
-
-
(count>= 8){ -
waiteString= -
( intt= 0;t<8;t++){ -
waiteString=waiteString+writes.charAt(t); -
} -
-
-
(writes.length()> 8){ -
tranString= -
( intt= 8;t<writes.length();t++){ -
tranString=tranString+writes.charAt(t); -
} -
writes= -
writes=tranString; -
} -
writes= -
} -
-
count=count- -
intw=changeString(waiteString); //得到String转化为int -
-
fos.write(intw); -
} -
-
count=count+SaveCode[i].n; -
writes=writes+SaveCode[i].node; -
i++; -
} -
} -
-
(count> 0){ -
waiteString= -
( intt= 0;t<8;t++){ -
(t<writes.length()){ -
waiteString=waiteString+writes.charAt(t); -
} -
waiteString=waiteString+ -
} -
} -
fos.write(changeString(waiteString)); -
System.out.println( -
} -
-
-
-
int changeString(String s){ -
(( int)s.charAt(0)-48)*128+((int)s.charAt(1)-48)*64+((int)s.charAt(2)-48)*32 -
+(( -
+(( - }
③ 将源文件中的所有byte 转化为 01 哈夫曼编码,写入压缩文件
- //再次读入文件的信息,对应每一个字节写入编码
-
count= -
writes = -
tranString= -
idata=fis.read(); -
-
((fis.available()> 0)||(count>=8)){ -
-
(count>= 8){ -
waiteString= -
( intt= 0;t<8;t++){ -
waiteString=waiteString+writes.charAt(t); -
} -
-
-
(writes.length()> 8){ -
tranString= -
( intt= 8;t<writes.length();t++){ -
tranString=tranString+writes.charAt(t); -
} -
writes= -
writes=tranString; -
} -
writes= -
} -
-
-
count=count- -
intw=changeString(waiteString); -
fos.write(intw); -
} -
-
count=count+SaveCode[idata].n; -
writes=writes+SaveCode[idata].node; -
idata=fis.read(); -
} -
} -
count=count+SaveCode[idata].n; -
writes=writes+SaveCode[idata].node; -
-
endsint= 0; -
(count> 0){ -
waiteString= -
( intt= 0;t<8;t++){ -
(t<writes.length()){ -
waiteString=waiteString+writes.charAt(t); -
} -
waiteString=waiteString+ -
endsint++; -
} -
} -
fos.write(changeString(waiteString));
如此一来,整个的压缩过程就完毕了。
解压缩过程:
①
- //读入文件中的每一个编码的长度
-
( inti= 0;i<256;i++){ -
Code hC= Code(); -
hC.n=fis.read(); -
hC.node= -
SaveCode[i]=hC; -
}
② 读入每一个字节所对应的编码
- int
i= 0; -
count= 0; -
String coms= -
-
(i< 256){ -
-
(coms.length()>=SaveCode[i].n){ -
( intt= 0;t<SaveCode[i].n;t++){//SaveCode[i].node取得该编码 -
SaveCode[i].node=SaveCode[i].node+coms.charAt(t); -
} -
-
-
String coms2= -
( intt=SaveCode[i].n;t<coms.length();t++){ -
coms2=coms2+coms.charAt(t); -
} -
coms= -
coms=coms2; -
i++; -
} -
String -
coms=coms+IntToString(fis.read()); -
} -
}
③ 读入文件的哈夫曼编码
得注意的事,前面我们又补0 的位,所以在我们读取的时候,记得把之前所补得东西,给删除掉就 OK 了。
- String
rsrg; //存转换成的Sting -
String compString= -
-
-
(search(compString)>= 0){ -
cint=search(compString); -
fos.write(cint); -
-
String compString2= -
( intt=SaveCode[cint].n;t<compString.length();t++){ -
compString2=compString2+compString.charAt(t); -
} -
compString= -
compString=compString2; -
-
} -
compString=compString+IntToString(fis.read()); -
} -
} -
-
-
-
cint=fis.read(); -
String compString2= -
( intt= 0;t<compString.length()-cint;t++){ -
compString2=compString2+compString.charAt(t); -
} -
compString=compString2; -
-
-
-
(compString.length()> 0){ -
ccint=search(compString); -
fos.write(ccint); -
compString2= -
( intt=SaveCode[ccint].n;t<compString.length();t++){ -
compString2=compString2+compString.charAt(t); -
} -
compString= -
compString=compString2; -
}