欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java实现哈夫曼压缩的实例

程序员文章站 2023-11-29 11:59:52
哈夫曼压缩的原理: 通过统计文件中每个字节出现的频率,将8位的01串转换为位数较短的哈夫曼编码. 其中哈夫曼编码是根据文件中字节出现的频率构建的,其中出现频...

哈夫曼压缩的原理:

通过统计文件中每个字节出现的频率,将8位的01串转换为位数较短的哈夫曼编码.

其中哈夫曼编码是根据文件中字节出现的频率构建的,其中出现频率越高的字节,其路径长度越短;

出现频率越低的字节其路径长度越长.从而达到压缩的目的.

如何构造哈夫曼树?

一.  定义字节类 

        我的字节类定义了一下属性   

   public int data;//节点数据 
public int weight;//该节点的权值 
public int point;//该节点所在的左右位置 0-左 1-右 
private nodedata parent;//父节点引用 
private nodedata left;//左节点引用 
private nodedata right;//右节点引用 

二.建哈夫曼树

1.定义一个存储字节信息的数组: int array_bytes[256]; 

  其中数组的下标[0,256)代表字节数值(一个字节8位,其值在[0,256)范围内);数组存储字节出现的次数.

2.遍历要压缩的文件,统计字节出现的次数. 

 inputstream data = new fileinputstream(path); 
 /******** 文件中字符个数 ********/ 
 int number = data.available(); 
 for (int i = 0; i < number; i++) { 
int b = data.read(); 
array_bytes[b] ++; 
 } 
data.close(); 

3.将字节类对象存入优先队列

4.运用hashmap 构造码表

哈夫曼压缩代码如下:

1.字节类

package compressfile; 
/** 
 * 节点数据 
 * 功能:定义数据,及其哈夫曼编码 
 * @author andrew 
 * 
 */ 
public class nodedata { 
  public nodedata(){ 
     
  } 
  public int data;//节点数据 
  public int weight;//该节点的权值 
  public int point;//该节点所在的左右位置 0-左 1-右 
  private nodedata parent; 
  private nodedata left; 
  private nodedata right; 
   
  public int getdata(){ 
    return data; 
  } 
  public nodedata getparent() { 
    return parent; 
  } 
  public void setparent(nodedata parent) { 
    this.parent = parent; 
  } 
  public nodedata getleft() { 
    return left; 
  } 
  public void setleft(nodedata left) { 
    this.left = left; 
  } 
  public nodedata getright() { 
    return right; 
  } 
  public void setright(nodedata right) { 
    this.right = right; 
  } 
} 

2.统计字节的类,并生成码表

package compressfile; 
 
import java.io.bufferedinputstream; 
import java.io.fileinputstream; 
import java.io.ioexception; 
import java.io.inputstream; 
import java.util.arraylist; 
import java.util.comparator; 
import java.util.hashmap; 
import java.util.list; 
import java.util.map; 
import java.util.priorityqueue; 
 
 
 
/** 
 * 统计指定文件中每个字节数 功能:定义数组,将文件中的字节类型及个数写入数组 
 * 创建优先队列和哈夫曼树 
 * @author andrew 
 * 
 */ 
public class statisticbytes { 
 
   
  /** 
   * 第一步: 
   * 统计文件中字节的方法 
   * 
   * @param path 
   *      所统计的文件路径 
   * @return 字节个数数组 
   */ 
  private int[] statistic(string path) { 
    /******储存字节数据的数组--索引值代表字节类型-存储值代表权值******/ 
    int[] array_bytes = new int[256]; 
    try { 
      inputstream data = new fileinputstream(path); 
      bufferedinputstream buffered = new bufferedinputstream(data); 
      /******** 文件中字符个数 ********/ 
      int number = data.available(); 
      system.out.println("字节个数》》》"+number); 
      for (int i = 0; i < number; i++) { 
        int b = data.read(); 
        array_bytes[b] ++; 
      } 
       
      data.close(); 
    } catch (ioexception e) { 
      e.printstacktrace(); 
    } 
    return array_bytes; 
  } 
  /** 
   * 第二步: 
   * 根据统计的字节数组创建优先队列 
   * @param array 统计文件字节的数组 
   * @return    优先队列 
   */ 
  private priorityqueue<nodedata> createqueue(int[] array){ 
    //定义优先队列,根据数据的权值排序从小到大 
    priorityqueue<nodedata> queue =  
        new priorityqueue<nodedata>(array.length,new comparator<nodedata>(){ 
      public int compare(nodedata o1, nodedata o2) { 
        return o1.weight - o2.weight; 
      } 
    }); 
     
    for(int i=0; i<array.length; i++){ 
      if(0 != array[i]){ 
        nodedata node = new nodedata(); 
        node.data = i;//设置该节点的数据 
        node.weight = array[i];//设置该节点的权值 
        queue.add(node); 
      } 
    } 
    return queue; 
  } 
  /** 
   * 第三步: 
   * 根据优先队列创建哈夫曼树 
   * @param queue  优先队列  
   * @return     哈夫曼树根节点 
   */ 
  private nodedata createtree(priorityqueue<nodedata> queue){ 
    while(queue.size() > 1){ 
       
      nodedata left = queue.poll();//取得左节点 
      nodedata right = queue.poll();//取得右节点 
       
      nodedata root = new nodedata();//创建新节点 
      root.weight = left.weight + right.weight; 
       
      root.setleft(left); 
      root.setright(right); 
      left.setparent(root); 
      right.setparent(root); 
       
      left.point = 0; 
      right.point = 1; 
       
      queue.add(root); 
    } 
    nodedata firstnode = queue.poll(); 
    return firstnode; 
  } 
  /** 
   * 第四步: 
   * 寻找叶节点并获得哈夫曼编码 
   * @param root  根节点 
   */ 
  private void achievehfmcode(nodedata root){ 
     
    if(null == root.getleft() && null == root.getright()){ 
      array_str[root.data] = this.achieveleafcode(root); 
      /** 
       * 
       * 得到将文件转换为哈夫曼编码后的文集长度 
       * 文件长度 = 一种编码的长度 * 该编码出现的次数 
       */ 
      writefile.size_file += (array_str[root.data].length())*(root.weight); 
    } 
    if(null != root.getleft()){ 
      achievehfmcode(root.getleft()); 
    } 
    if(null != root.getright()){ 
      achievehfmcode(root.getright()); 
    } 
  } 
  /** 
   * 根据某叶节点获得该叶节点的哈夫曼编码 
   * @param leafnode  叶节点对象 
   */ 
  private string achieveleafcode(nodedata leafnode){ 
    string str = ""; 
    /*****************存储节点 01 编码的队列****************/ 
    list<integer> list_hfmcode = new arraylist<integer>(); 
    list_hfmcode.add(leafnode.point); 
    nodedata parent = leafnode.getparent(); 
     
    while(null != parent){ 
      list_hfmcode.add(parent.point); 
      parent = parent.getparent(); 
    } 
     
    int size = list_hfmcode.size(); 
    for(int i=size-2; i>=0; i--){ 
      str += list_hfmcode.get(i); 
    } 
    system.out.println(leafnode.weight+"的哈夫曼编码为>>>"+str); 
    return str; 
  } 
  /** 
   * 第五步: 
   * 根据获得的哈夫曼表创建 码表 
   * integer 为字节byte [0~256) 
   * string 为哈夫曼编码 
   * @return 码表 
   */ 
  public map<integer,string> createmap(){ 
    int[] hfm_codes = this.statistic("f:\\java\\压缩前.txt");//获得文件字节信息 
    priorityqueue<nodedata> queue = this.createqueue(hfm_codes);//获得优先队列 
    /* 
     * 获得哈夫曼树的根节点, 
     * this.createtree(queue)方法调用之后(含有poll()),queue.size()=0 
     */ 
    nodedata root = this.createtree(queue); 
    this.achievehfmcode(root);//获得哈夫曼编码并将其存入数组中 
     
    map<integer,string> map = new hashmap<integer,string>(); 
    for(int i=0; i<256; i++){ 
      if(null != array_str[i]){ 
        map.put(i, array_str[i]); 
      } 
    } 
    return map; 
  } 
  /** 
   * 存储字节哈夫曼编码的数组 
   * 下标:字节数据 
   * 元素:哈夫曼编码 
   */ 
  public string[] array_str = new string[256]; 
} 

3.将码表写入压缩文件并压缩正文

package compressfile; 
 
import java.io.bufferedinputstream; 
import java.io.bufferedoutputstream; 
import java.io.dataoutputstream; 
import java.io.fileinputstream; 
import java.io.fileoutputstream; 
import java.io.ioexception; 
import java.util.iterator; 
import java.util.map; 
import java.util.set; 
 
/** 
 * 将码表和文件写入新的文件中 
 * @author andrew 
 * 
 */ 
public class writefile { 
 
  // 实例化创建码表类对象 
  private statisticbytes statistic = new statisticbytes(); 
  private map<integer, string> map = statistic.createmap();// 获得码表 
  // 字节接收变量,接收哈夫曼编码连接够8位时转换的字节 
  private int exmpcode; 
  public static int size_file; 
 
  public static void main(string[] args) { 
    writefile writefile = new writefile(); 
    writefile.init(); 
  } 
 
  private void init() { 
 
    string filepath = "f:\\java\\压缩后.txt"; 
    this.writefile(filepath); 
  } 
 
  /** 
   * 第一步: 向文件中写入码表 
   * 
   * @param dataout 
   *      基本数据流 
   */ 
  private void writecodetable(dataoutputstream dataout) { 
    set<integer> set = map.keyset(); 
    iterator<integer> p = set.iterator(); 
 
    try { 
      //向文件中写入码表的长度 
      dataout.writeint(map.size()); 
      while (p.hasnext()) { 
        integer key = p.next(); 
        string hfmcode = map.get(key); 
 
        dataout.writeint(key);//写入字节 
        //写入哈夫曼编码的长度 
        dataout.writebyte(hfmcode.length()); 
        for(int i=0; i<hfmcode.length(); i++){ 
          dataout.writechar(hfmcode.charat(i));//写入哈夫曼编码 
        } 
      } 
    } catch (ioexception e) { 
      e.printstacktrace(); 
    } 
  } 
 
  /** 
   * 第二步: 向压缩文件中写入编码 
   * 
   * @param path 
   */ 
  private void writefile(string path) { 
 
    try { 
      // 输入流 
      fileinputstream in = new fileinputstream("f:\\java\\压缩前.txt"); 
      bufferedinputstream bin = new bufferedinputstream(in); 
      // 输出流 
      fileoutputstream out = new fileoutputstream(path); 
      dataoutputstream dataout = new dataoutputstream(out); 
      bufferedoutputstream bout = new bufferedoutputstream(out); 
      // 向文件中写入码表 
      this.writecodetable(dataout); 
      /********************写入补零个数*********************/ 
      if(0 != size_file % 8){ 
        dataout.writebyte(8 - (size_file % 8)); 
      } 
       
      string transstring = "";//中转字符串,存储字符串直到size大于8 
      string waitestring = "";//转化字符串, 
       
      int size_file = in.available(); 
      for(int i=0; i<size_file; i++){ 
        int j = bin.read(); 
        system.out.println("]]]]]]]]]]]>>"); 
        waitestring = this.changestringtobyte(transstring + statistic.array_str[j]); 
        if(waitestring != ""){  
          bout.write(exmpcode); 
          transstring = waitestring; 
        }else{ 
          transstring += statistic.array_str[j]; 
        } 
      } 
      if("" != transstring){ 
        int num = 8-transstring.length()%8; 
        for(int i=0; i<num; i++){ 
          transstring += 0; 
        } 
      } 
      transstring = this.changestringtobyte(transstring); 
      bout.write(exmpcode); 
       
      bin.close(); 
      bout.flush(); 
      bout.close(); 
      out.close(); 
    } catch (ioexception e) { 
      e.printstacktrace(); 
    } 
  } 
 
  /** 
   * 附属第二步: 
   * 将字符串转化为byte 
   * 
   * @param str 
   *      要转化的字符串 
   * @return 如果str的长度大于8返回一个截取前8位后的str 
   *     否则返回"" 
   */ 
  private string changestringtobyte(string str) { 
    if (8 <= str.length()) { 
      exmpcode = ((str.charat(0) - 48) * 128 
          + (str.charat(1) - 48) * 64 + (str.charat(2) - 48) * 32 
          + (str.charat(3) - 48) * 16 + (str.charat(4) - 48) * 8 
          + (str.charat(5) - 48) * 4 + (str.charat(6) - 48) * 2  
          + (str.charat(7) - 48)); 
      str = str.substring(8); 
      return str; 
    } 
    return ""; 
  } 
} 

二.哈夫曼解压 

   原理:将压缩的逆向,即你是如何压缩的就怎样去解压。相当于你根据自己定义的协议进行压缩与解压缩文件。

代码如下:

package decompressionfile; 
 
import java.io.datainputstream; 
import java.io.fileinputstream; 
import java.io.fileoutputstream; 
import java.io.ioexception; 
import java.io.inputstream; 
import java.io.outputstream; 
import java.util.arraylist; 
import java.util.hashmap; 
import java.util.iterator; 
import java.util.list; 
import java.util.map; 
import java.util.set; 
 
/** 
 * 解压文件 从压缩文件中读入数据解压 
 * 
 * @author andrew 
 * 
 */ 
public class readfile { 
  /** 
   * 码表 integter: 字节 [0,255) string: 哈夫曼编码 
   */ 
  private map<integer, string> code_map = new hashmap<integer, string>(); 
 
  public static void main(string[] args) { 
    readfile readfile = new readfile(); 
    readfile.readfile(); 
  } 
 
  /** 
   * 第一步: 从文件中读出码表 
   * 
   * @param datainput 
   *      基本数据输入流 
   * 
   */ 
  private void readmap(datainputstream datainput) { 
 
    try { 
      // 读出码表的长度 
      int size_map = datainput.readint(); 
      /**************** 读出码表信息 ************************************/ 
      for (int i = 0; i < size_map; i++) { 
        int data = datainput.readint();// 读入字节信息 
        string str = "";// 哈夫曼编码 
        // 哈夫曼编码长度,存储时以字符写入 
        byte codesize = datainput.readbyte(); 
        for (byte j = 0; j < codesize; j++) { 
          str += datainput.readchar(); 
        } 
        system.out.println("&&&&&&&&&&>>>>"+data+"!!!!!!!>>"+str); 
        code_map.put(data, str); 
      } 
      /***************************************************************/ 
    } catch (ioexception e) { 
      e.printstacktrace(); 
    } 
  } 
 
  /** 
   * 第二步: 解压正式文件 
   */ 
  private void readfile() { 
    try { 
      // 文件输入流 
      inputstream input = new fileinputstream("f:\\java\\压缩后.txt"); 
      // bufferedinputstream bin = new bufferedinputstream(input); 
      datainputstream dinput = new datainputstream(input); 
      // 调用读出码表的方法 
      this.readmap(dinput); 
      byte zerofill = dinput.readbyte();// 读出文件补零个数 
      system.out.println("补零个数》》》>>>>"+zerofill); 
      // 文件输出流 
      outputstream out = new fileoutputstream("f:\\java\\解压后.txt"); 
       
      string transstring = "";//接收用于匹配码表中哈夫曼编码的字符串 
      string waitestring = "";//接收截取哈夫曼编码后剩余的字符串 
       
      /***********************************不耗内存的方法*****************************************/ 
      int readcode = input.read();//从压缩文件中读出一个数据 
      int size = input.available(); 
      for(int j=0; j<=size; j++){ 
        system.out.println("readcodereadcodereadcode》》>>>>"+readcode); 
        waitestring += this.changeinttobinarystring(readcode);//将读到的整数转化为01字符串 
        for(int i=0; i<waitestring.length(); i++){ 
          //将从文件中读出的01串一个一个字节的截取添加与码表中的哈夫曼编码比较 
          transstring += waitestring.charat(i); 
          if(this.searchhc(transstring, out)){ 
//           waitestring = waitestring.substring( i+1 ); 
            transstring = ""; 
          } 
        } 
        waitestring = ""; 
        readcode = input.read(); 
        if(j == size-1){ 
          break; 
        } 
      } 
      /************************************处理最后一个字***************************************/ 
//     int lastbyte = input.read(); 
      string lastword = this.changeinttobinarystring(readcode); 
      waitestring = transstring + lastword.substring(0, 8-zerofill); 
      transstring = ""; 
      for(int i=0; i<waitestring.length(); i++){ 
        //将从文件中读出的01串一个一个字节的截取添加与码表中的哈夫曼编码比较 
        transstring += waitestring.charat(i); 
        if(this.searchhc(transstring, out)){ 
//         waitestring = waitestring.substring( i+1 ); 
          transstring = ""; 
        } 
      } 
//     this.searchhc(transstring, out); 
      /***********************************队列法,耗内存****************************************/ 
//     int readcode = input.read();//从压缩文件中读出一个数据 
//     list<character> list = new arraylist<character>(); 
//     while(-1 != readcode){ 
//       string str = this.changeinttobinarystring(readcode); 
//       for(int i=0; i<str.length(); i++){ 
//         list.add(str.charat(i)); 
//       } 
//       readcode = input.read(); 
//     } 
//     for(int j=0; j<list.size()-zerofill; j++){ 
//       transstring += list.get(j); 
//       if(this.searchhc(transstring, out)){ 
//         transstring = ""; 
//       } 
//     } 
      /*****************************************************************************************/ 
      input.close(); 
      out.close(); 
    } catch (ioexception e) { 
      e.printstacktrace(); 
    } 
  } 
  /** 
   * 将从文件中读到的01 串与码表中的哈夫曼编码比较 
   * 若在码表中含有与之对应的哈夫曼编码则将码表中对应的 
   * 数据写入解压文件,否则不写入 
   * @param str 从文件中读到的01 字符串 
   * @param out 文件输出流 
   * @return   若写入文件返回true,否则返回false 
   * @throws ioexception 写入发生错误时抛出异常 
   */ 
  private boolean searchhc(string str, outputstream out) throws ioexception{ 
    set<integer> set = code_map.keyset(); 
    iterator<integer> p = set.iterator(); 
    while (p.hasnext()) { 
      integer key = p.next();//获得码表中的字节数据 
      string hfmcode = code_map.get(key);//获得哈夫曼编码 
      if(hfmcode.equals(str)){ 
        out.write(key); 
        return true; 
      } 
    } 
    return false; 
  } 
  /** 
   * 根据 "除2取余,逆序排列"法 
   * 将十进制数字转化为二进制字符串 
   * @param a  要转化的数字 
   * @return  01 字符串 
   */ 
  private string changeinttobinarystring(int a) { 
    int b = a; 
    int count = 0; //记录 a 可转化为01串的个数,在不够8为时便于补零 
    string str = "";// 逆序二进制字符串 
    string exmpstr = "";// 顺序二进制字符串 
 
    while (a != 0) { 
      b = a/2; 
      if (a % 2 != 0) { 
        str += 1; 
      } else { 
        str += 0; 
      } 
      a = b; 
      count++; 
    } 
    //不够8位的地方补零 
    for (int i = 0; i < 8 - count; i++) { 
      str += 0; 
    } 
    //将转化后的二进制字符串正序 
    for (int j = 7; j >= 0; j--) { 
      system.out.print(str.charat(j)); 
      exmpstr += str.charat(j); 
    } 
    system.out.println("转化后的字符串>>>>>>>>>"+exmpstr); 
    return exmpstr; 
  } 
}