哈夫曼压缩总结

哈夫曼压缩:
进行哈夫曼压缩首先要知道什么是哈夫曼树。
哈夫曼树是最优的二叉树,最优指的是带权路径最短的树。
那么如何建立一个哈夫曼树呢?
实现步骤:
1.给定大小无序的数据,首先把数据存储起来,然后再对数据进行排序。
2.创建节点类,节点类中要包含(左、右子节点的引用和数据值)
3.构建哈夫曼树:
每次取两个最小的数据,实例化两个节点,建立一个新节点,其数据值为取出的两个数据值之和,并将新节点作为两个节点的
父节点加到存储容器中,并让容器移除取出的两个节点。重复上述步骤,知道容器中只有一个节点,它就是哈夫曼树的根节点。
哈夫曼压缩思路:
1.读文件,统计每个字符出现的次数。将次数存储在数组中。
2.根据出现的次数对其进行排序(通过优先队列),建立节点将其加入优先队列中。
3.构建哈弗曼树。将队列中的前两个元素移除并得到其出现次数构成新节点,加入队列中。重复以上步骤,直到队列中没有元素。
4.遍历树,得到每个叶子节点的编码。
5.写头文件,对编码进行处理,不足8位在前补0,够8位则写入。
我在压缩时写头文件主要写了各个编码的长度,各个编码。
       写文件内容,8位8位的写入,不足8位则补0。
   6.解压
     首先把编码长度读出,接着读编码。这里需要注意,我在写头文件时并没把该读多少字节写入,但前面我已经把各个编码长度读出来了
     所以我还是8位8位的读,当读出字符串长度大于SaveCode[i].length的长度时就把它赋给SaveCode[i].node,这就是它的编码
     读内容时是同样的道理,根据读到的字符串与编码进行匹配,匹配成功时读出,不成功就继续读。
下面是哈夫曼压缩代码:
public class HfmMain {
public int bytecount[]=new int [256];//创建存储压缩内容中每个字符出现的次数
static hfmNode root;
static Code[] SaveCode=new Code[256];
public static void main(String []args) throws Exception{
HfmMain hm = new HfmMain();
String filename = "C:\\Users\\aa\\Desktop\\蓝杰 java\\sss.txt";
String name="C:\\Users\\aa\\Desktop\\蓝杰 java\\aaa.txt";
hm.read(filename);
hm.createTree();
hm.printTree(root);
hm.createSaveCode();
hm.getMB(root, "");
hm.printSaveCode();
UtilCode uc=new UtilCode(SaveCode);
uc.writefile(filename);
DeCode dc=new DeCode(SaveCode);
dc.Decode(filename+".hfrq", name);
}
/**
* 创建读文件的方法
* @param filename
* @throws Exception
*/
public void read(String filename) throws Exception{
//实例化输入流对象
InputStream ins = new FileInputStream(filename);
while(ins.available()>0){
int i = ins.read();
bytecount[i]++;
}
}
/**
* 创建哈弗曼树
*/
public void createTree(){
PriorityQueue<hfmNode> hfmqueue = new PriorityQueue<hfmNode>();
for(int i=0;i<bytecount.length;i++){
if(bytecount[i]!=0){
hfmNode node = new hfmNode(i,bytecount[i]);
hfmqueue.add(node); //向优先队列中加入节点
}
}
while(hfmqueue.size()>1){
hfmNode min1 = hfmqueue.poll();
hfmNode min2 = hfmqueue.poll();
hfmNode result = new hfmNode(0,min1.times+min2.times);
result.lchild=min1;
result.rchild=min2;
hfmqueue.add(result);
}
root = hfmqueue.peek();
}
/**
* 给叶子节点加编码的方法
*/
public void getMB(hfmNode node,String s){
if(node.lchild==null&&node.rchild==null){
Code c = new Code();
c.length=s.length();
c.code=s;
SaveCode[node.obj]=c;
}
if(node.lchild!=null){
getMB(node.lchild,s+'0');
}
if(node.rchild!=null){
getMB(node.rchild,s+'1');
}
}
/**
* 输出树
* @param node
*/
public void printTree(hfmNode node){
if(node==null){
return ;
}else{
System.out.println("次数: "+node.times+"   元素:  "+node.obj);
printTree(node.lchild);
printTree(node.rchild);
}
}
/**
* 初始化存储编码的数组
*/
public void createSaveCode(){
for(int i=0;i<256;i++){
Code c=new Code();
c.code="";
c.length=0;
SaveCode[i]=c;
}
}
public void printSaveCode(){
for(int i=0;i<256;i++){
System.out.println(i+" :"+SaveCode[i].length+"  "+SaveCode[i].code);
}
}
}
/**
* 压缩的方法
* @author lsx
*/
public class UtilCode {
Code SaveCode[];
/**
* 构造函数
* @param bytecount 表示存储字符出现次数的数组
*/
public UtilCode(Code SaveCode[]){
this.SaveCode=SaveCode;
}
/**
* 写文件的方法
* @throws Exception
*/
public void writefile(String filename) throws Exception{
//创建输入流对象
InputStream ins = new FileInputStream(filename);
//创建输出流对象
OutputStream os = new FileOutputStream(filename+".hfrq");
//写头信息
os.write((int)'h');
os.write((int)'f');
os.write((int)'r');
os.write((int)'q');
//写各个字符的编码长度
for(int i=0;i<256;i++){
os.write(SaveCode[i].length);
System.out.println(i+"点的编码长:"+SaveCode[i].length);
}
//写编码内容
int i=0;
int count=0; //当前字符串长度
String write="";
String transwrite="";
String waitwrite="";
while((i<256)||count>=8){
if(count>8){
//将前8位写入
waitwrite="";
for(int t=0;t<8;t++){
waitwrite+=write.charAt(t);
}
if(write.length()>8){
transwrite="";
for(int t=8;t<write.length();t++){
transwrite+=write.charAt(t);
}
write="";
write=transwrite;
}else{
write="";
}
count-=8;
os.write(changeString(waitwrite));
System.out.println("写入了头--->"+waitwrite+" "+changeString(waitwrite));
}else{
count+=SaveCode[i].length;
write+=SaveCode[i].code;
i++;
}
}
if(count>0){
waitwrite="";
for(int t=0;t<8;t++){
if(t<write.length()){
waitwrite+=write.charAt(t);
}else{
waitwrite+='0';
}
}
os.write(changeString(waitwrite));
System.out.println("写入了头--->"+waitwrite+" "+changeString(waitwrite));
}
// **********************************************************************************
//写文件内容
count=0;
write="";
transwrite="";
waitwrite="";
int data = ins.read();
while(ins.available()>0||count>=8){
if(count>8){
waitwrite="";
for(int t=0;t<8;t++){
waitwrite+=write.charAt(t);
}
if(write.length()>8){
transwrite="";
for(int t=8;t<write.length();t++){
transwrite+=write.charAt(t);
}
write="";
write=transwrite;
}else{
write="";
}
count-=8;
os.write(changeString(waitwrite));
System.out.println("写入了内容-->"+waitwrite+"  "+changeString(waitwrite));
}else{
count+=SaveCode[data].length;
write+=SaveCode[data].code;
data=ins.read();
}
}
count+=SaveCode[data].length;
write+=SaveCode[data].code;
int end = 0;
if(count>0){
waitwrite="";
for(int t=0;t<8;t++){
if(t<write.length()){
waitwrite+=write.charAt(t);
}else{
waitwrite+='0';
end++;
}
}
os.write(changeString(waitwrite));
System.out.println("写入了内容-->"+waitwrite+"  "+changeString(waitwrite));
}
os.write(end);
System.out.println("压缩完成");
}
/**
* 将8位的字符串转换成一个整数
*/
public int changeString(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);
}
}
public class DeCode {
Code SaveCode[];
/**
* 构造方法
* @param SaveCode
*/
public DeCode(Code SaveCode[]){
this.SaveCode=SaveCode;
}
/**
* 解压方法
* name要解压的文件名
* filename解压到的文件名
* @throws Exception
*/
public void Decode(String name,String filename) throws Exception{
//创建输入流对象
InputStream ins=new FileInputStream(name);
// BufferedInputStream bis=new BufferedInputStream(ins);
if(ins.read()!='h'){
System.out.println(" Not a .hfrq file ");
}
if(ins.read()!='f'){
System.out.println(" Not a .hfrq file ");
}
if(ins.read()!='r'){
System.out.println(" Not a .hfrq file ");
}
if(ins.read()!='q'){
System.out.println(" Not a .hfrq file ");
}
//读SaveCode中的编码长度
for(int i=0;i<256;i++){
Code c=new Code();
//读编码长度
c.length=ins.read();
System.out.println("SaveCode["+i+"]:"+c.length);
c.code="";
//将编码对象存到数组中
SaveCode[i]=c;
}
System.out.println("各个点都有了值");
int i=0;
String coms="";
//读SaveCode中的编码
while (i<256){
       if (coms.length()>=SaveCode[i].length){
       //先把这b[i].n位给b[i].node
      for (int t=0;t<SaveCode[i].length;t++){
      SaveCode[i].code=SaveCode[i].code+coms.charAt(t);
      }
      System.out.println("b["+i+"]:"+SaveCode[i].length+" "+SaveCode[i].code);
      //把coms前这几位去掉
      String coms2="";
      for (int t=SaveCode[i].length;t<coms.length();t++){
      coms2=coms2+coms.charAt(t);
      }
      coms="";
      coms=coms2;
      i++;
       }else{
        coms=coms+changeint(ins.read());
       }
       }
//创建输出流对象
OutputStream os=new FileOutputStream(filename);
String compString="";
while(ins.available()>1){
if(search(compString)>=0){
int intw=search(compString); //得到了当前字符的acsii码
System.out.println("写入了:"+"int:"+intw+" "+changeint(intw)+" ="+compString);
os.write(intw); //将当前字符写入文件
String compString2="";
//删除前n个元素
for(int t=SaveCode[intw].length;t<compString.length();t++){
compString2+=compString.charAt(t);
}
compString="";
compString=compString2;
}else{
compString+=changeint(ins.read());
}
}
int end = ins.read(); //最后一个,表示补0的个数
String compString2="";
for(int t=0;t<compString.length()-end;t++){
compString2+=compString.charAt(t);
}
compString = "";
compString = compString2;
while (compString.length()>0){
   int ccint=search(compString);
   os.write(ccint);  
//    System.out.println("写入了:"+compString);
   compString2="";
          for (int t=SaveCode[ccint].length;t<compString.length();t++){
       compString2=compString2+compString.charAt(t);
          }
          compString="";
          compString=compString2;
  }
ins.close();
os.flush();
os.close();
  System.out.println("解码完毕!");
}
/**
* 将1个整数转换成8位字符串
*/
public String changeint(int n){
int n0=n;
String s="";
for(int i=0;i<8;i++){
if((n0%2)==0){
s='0'+s;
}else if((n0%2)==1){
s='1'+s;
}
n0=n0/2;
}
return s;
}
/**
* 找到编码所对应的位置
*/
public int match(String s){
for(int i=0;i<256;i++){
if(SaveCode[i].length==s.length()&&(SaveCode[i].code.equals(s))){
return i;
}
}
return -1;
}
public int search(String s){
int i;
int num=-1;
String ss="";
for(i=0;i<s.length();i++){
ss+=s.charAt(i);
num=match(ss);
if(num>=0){
break;
}
}
if(i<s.length()){
return num;
}else {
return -1;
}
}
}
出现的问题:最后的时候要把输入输出流关闭,否则在解压过程中读取文件内容时最后一位会没有与之匹配的编码,从而导致数组下标越界,而解压
的文件会少写入一位。
要点:理清思路,写文件时要明白自己写了哪些内容,是按什么顺序写入的,读的时候按顺序读出。

你可能感兴趣的:(总结)