浅谈哈夫曼压缩
由于没能参加大家的讨论来实现这块内容,所以自己在理解或者是理清思路上费了很大的劲,才弄明白哈夫曼的压缩与解压每一步到底是怎么实现的,或者说他的原理是什么,不管怎样,在分参考了前辈的代码及努力之下,我的哈夫曼压缩终于完成啦。。。。
首先,先简单的说明一下哈夫曼实现压缩的原理。
在哈夫曼压缩中,最重要的前提大概就是构造一颗哈夫曼树并给他编码了,所谓哈夫曼树就是一个二叉树中所有叶子节点的带权路径长度最小的树。
例:对字符串AABDCDDCE编码
根据各字母出现的频率W={A,B,C,D,E}={2,1,2,3,1},以频率为权值构造哈夫曼树
1.选取权值最小的树作为左右子树构造一颗新的二叉树,此二叉树的根节点的权值为左右子树根节点的权值之和;
2.将新的而二叉树加入到W中,将原来两颗根节点权值最小的树删除;
3.重复1,2步骤,直到W中只含一棵树为止,此树就是哈夫曼树;
构造树完成之后按左0右1给结点编码,如图:
即 A:00 B:100 C:01 D:11 E:101
所以原字符串经过哈夫曼压缩之后就变为 001001101111101101 存储在压缩后的文件中,计算机中所有的数据最后都是转换为二进制位去存储的,所以一个一个字节型的数据要转换为8个01位,当我们直接将编码按照计算机的存储规则用位的方法写入进去就能实现压缩了。
压缩:
1.先把每个编码的长度写入文件
// 先把每个字节的编码的长度写入文件(字节的上限为256)
for(int i = 0; i < hfmCode.length; i++) {
if (hfmCode[i] == null){ //某个字节出现的次数为0次
bos.write(0);
bos.flush();
}
else{
//写入每个编码的长度
bos.write(hfmCode[i].length());
bos.flush();
}
}
2.把每个字节的编码写入文件
int i=0;//第i个字节
int count=0;//缓冲区的字符个数
int num=0;//要补0的个数
String tranString="";//缓冲区的01字符串
String waitString="";//要写入的01字符串
while(i<256||count>=8){
//缓冲区的等待写入字符大于八
if(count>=8){
//返回一个新字符串
tranString=waitString.substring(0, 8);
//将八位转换为byte写入,write()方法的参数是个Int
bos.write(StringtoInt(tranString)); count=count-8;
//返回一个新的字符串
waitString=waitString.substring(8);
}
else {//一个个地读取,等待写入,此时读取的是保存编码的数组,所以读取到的是字节,对应到哈夫曼编码
if(hfmCode[i]!=null){//当hfmCode[]数组的第i个元素不为空时 count=count+hfmCode[i].length(); //将读取到的字节加到要写入的字符串
waitString=waitString+hfmCode[i];
}
i++;
}
}
//如果不满八位 要补0使其为8的整数倍 要计数补了多少个0
if(count>0){
//获得要补0的个数
num=8-waitString.length();
for(int k=0;k<num;k++){ waitString=waitString+"0";
}
bos.write(StringtoInt(waitString));
bos.write(num);//要写入补了几个0,以便于到时解压
}
else{
bos.write(0);//没有补入0
}
bos.flush();
3.压缩文件正文,将对应的字节编码写进去
//创建输入流读取压缩前的文件
FileInputStream fis=new FileInputStream(path1);
BufferedInputStream bis=new BufferedInputStream(fis);
count=0;//缓冲区的字符个数置0
num=0;
tranString="";//清空缓冲区的01字符串
waitString="";//清空要写入的01字符串
//当文件没有读完的时候
while(bis.available()>0||count>8){
//缓冲区的等待写入字符大于8
if(count>=8){
//返回一个新字符串
tranString=waitString.substring(0, 8);
//将八位转换为byte写入,write()方法的参数是个Int
bos.write(StringtoInt(tranString)); bos.flush();
count=count-8;
//返回一个新的字符串
waitString=waitString.substring(8);
}
else {//一个个地读取,等待写入,此时读取的是压缩前的原文件,所以读取到的是字节,对应到哈夫曼编码
int data=bis.read();
count=count+hfmCode[data].length(); //将读取到的字节加到要写入的字符串中 waitString=waitString+hfmCode[data]; data=bis.read();
}
}
//将count剩下的最后补0
if(count>0){
num=8-waitString.length();
for(int k=0;k<num;k++){
waitString=waitString+"0";
}
bos.write(StringtoInt(waitString));//写入补的0
bos.write(num);//要写入补了几个0 }
bos.flush();
bos.close();
将字符串转换为整型:
/**
* 将字符串转换为整形
* @param s 要转换的字符串
* @return 将转换的整型返回
*/
public int StringtoInt(String s) {
return((int)s.charAt(0)-48)*128+((int)s.charAt(1)-48)*64+((int)s.charAt(2)-48)*32+((int)s.charAt(3)-48)*16+((int)s.charAt(4)-48)*8+((int)s.charAt(5)-48)*4+((int)s.charAt(6)-48)*2+((int)s.charAt(7)-48);
}