Huffman編碼實現壓縮解壓縮

這是我們的課程中佈置的作業,找一些資料將作業完成,順便將其寫到博客,以後看起來也方便。

原理介紹

  • 什麼是Huffman壓縮

    Huffman( 哈夫曼 ) 算法在上世紀五十年代初提出來了,它是一種無損壓縮方法,在壓縮過程中不會丟失信息熵,而且可以證明 Huffman 算法在無損壓縮算法中是最優的。 Huffman 原理簡單,實現起來也不困難,在現在的主流壓縮軟件得到了廣泛的應用。對應用程序、重要資料等絕對不允許信息丟失的壓縮場合, Huffman 算法是非常好的選擇。

  • 怎麼實現Huffman壓縮
    哈夫曼壓縮是個無損的壓縮算法,一般用來壓縮文本和程序文件。哈夫曼壓縮屬於可變代碼長度算法一族。意思是個體符號(例如,文本文件中的字符)用一個特定長度的位序列替代。因此,在文件中出現頻率高的符號,使用短的位序列,而那些很少出現的符號,則用較長的位序列。

    1. 二叉樹
      在計算機科學中,二叉樹是每個結點最多有兩個子樹的有序樹。通常子樹的根被稱作 “ 左子樹 ” ( left subtree )和 “ 右子樹 ” ( right subtree )。
      二叉樹
    2. 哈夫曼編碼 (Huffman Coding)
      哈夫曼編碼是一種編碼方式,哈夫曼編碼是可變字長編碼 (VLC) 的一種。 uffman 於 1952 年提出一種編碼方法,該方法完全依據字符出現概率來構造異字頭的平均長 度最短的碼字,有時稱之爲最佳編碼,一般就叫作 Huffman 編碼。
  • Huffman編碼生成步驟
    1. 掃描要壓縮的文件,對字符出現的頻率進行計算。
    2. 把字符按出現的頻率進行排序,組成一個隊列。
    3. 把出現頻率最低(權值)的兩個字符作爲葉子節點,它們的權值之和爲根節點組成一棵樹。
    4. 把上面葉子節點的兩個字符從隊列中移除,並把它們組成的根節點加入到隊列。
    5. 把隊列重新進行排序。重複步驟 3、4、5 直到隊列中只有一個節點爲止。
    6. 把這棵樹上的根節點定義爲 0 (可自行定義 0 或 1 )左邊爲 0 ,右邊爲 1 。這樣就可以得到每個葉子節點的哈夫曼編碼了。
      這裏寫圖片描述
      如 (a) 、 (b) 、 (c) 、 (d) 幾個圖,就可以將離散型的數據轉化爲樹型的了。
      如果假設樹的左邊用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 個二進制位
    而如果用它的哈弗曼編碼去存儲,只有110 三個二進制位。
    效果顯而易見。

編碼實現

  1. 流程圖
    編碼流程
Created with Raphaël 2.1.0開始讀入待壓縮文件,計算文件中各字符的權重根據權重構建Huffman樹根據Huffman樹獲得各個字符的HUffman編碼,並建立Huffman編碼的HashTable將字符總數、字符種數,以及Huffman樹寫入壓縮文件文件頭再次讀入待壓縮文件,根據其內容和coding hash table 將壓縮後的數據寫入文件結束
  1. 數據結構
    CharacterWeight:記錄字符值,以及其在待壓縮文件中的權重。
public class CharacterCode {
    private int weight;//字符值  
    private char character;//字符值 
    private String code;//其對應huffman編碼 
    } 

HuffmanNode:huffman樹中的節點信息。

public class HuffmanNode {
    private int parent;//父節點
    private int lChild;//左子
    private int rChild;//右子
    private int weight;//權重
    }
  1. 程序關鍵步驟
    • Huffman樹的構建
      Huffman樹的變量:ArrayList list;
      流程圖
Created with Raphaël 2.1.0開始i=0 n=字符的種數循環遍歷查找列表中權重最小的兩個node創建一個新的節點作爲找到的兩個權重最小的節點的父節點,並將該父節點的權重置爲權重最小的兩節點的權重和,將該節點加入數組中。i++i<n-1結束yesno

代碼

for(int i=0;i<list.size()-1;i++){  
            //w1 : the first min weight w2: the second min weight   
            //i1 : the first min weight index, i2: the second min weight index  
            int w1 = MAX_VALUE, w2=MAX_VALUE;   
            int i1 = 0, i2 = 0;  
            // find the two node with the minimum weight  
            for(int j=0;j<tree.size();j++){  
                HuffmanNode node = tree.get(j);  
                if(node.getWeight()< w1 && node.getParent()==-1){  
                    w2 = w1;  
                    w1 = node.getWeight();  
                    i2 = i1;  
                    i1 = j;  
                }  
                else if(node.getWeight()<w2 && node.getParent()==-1){  
                    w2 = node.getWeight();  
                    i2 = j;  
                }  
            }  
            //set the two node to be the children of a new node, and add the new node to the tree  
            HuffmanNode pNode = new HuffmanNode(w1+w2);  
            pNode.setlChild(i1);  
            pNode.setrChild(i2);  
            tree.add(pNode);  
            tree.get(i1).setParent(tree.indexOf(pNode));  
            tree.get(i2).setParent(tree.indexOf(pNode));}  
  • 根據Huffman 樹獲得Huffman編碼
    從葉子節點開始網上遍歷Huffman樹,直到到達根節點,根據當前節點爲其父節點的左兒子還是右兒子確定這一位值是0還是1。最後將依次獲得的0,1字符串反轉獲得Huffman編碼。
for(int i=0;i<list.size();i++){  
            HuffmanNode node = tree.get(i);  
            HuffmanNode pNode = tree.get(node.getParent());  
            String code ="";  
            while(true){  
                if(pNode.getlChild()==tree.indexOf(node)){  
                    code = "0"+code;  
                }  
                else if(pNode.getrChild() == tree.indexOf(node)){  
                    code = "1"+code;  
                }  
                else {  
                    System.out.println("Tree Node Error!!!");  
                    return null;  
                }  
                node=pNode;  
                if(node.getParent()!=-1)  
                    pNode=tree.get(node.getParent());  
                else   
                    break;  
            }  
            list.get(i).setCode(new String(code));  
        }  
  • 頭文件設計

    編碼 類型 字節數
    字符總數 Int 4
    字符種類數 Short 2
    葉子節點 char字符 short 父節點 3
    非葉子節點 Short 左兒子 short 右兒子 short父節點 6

    文件頭長度(單位: byte)
    l= 9n
    其中n 爲字符種類數。

    • 文件內容的編碼和寫入
Created with Raphaël 2.1.0開始將待壓縮文件讀入字符數組根據coding hash table 獲得huffman編碼字符串,並將該字符串添加到buff中查看buff,如果字符數大於8則將字符串轉換爲Short類型變量並寫入文件將寫入的字符從buff中刪除是否到達文件尾?結束yesno

代碼

while((temp=reader.read())!=-1){ //!= EOF     
            // get the code from the code table  
            String code = codeTable.get((char)temp);  
            c++;  
            if(c>=count/96){  
                System.out.print("=");  
                c=0;  
            }  
            try{  
                StringBuilder codeString = new StringBuilder(code);  
                outputStringBuffer.append(codeString);  
                while(outputStringBuffer.length()>8){  
                    out.write(Short.parseShort(outputStringBuffer.substring(0, 8),2));  
                    outputStringBuffer.delete(0, 8);  
                }  
            } catch(Exception e){  
                e.printStackTrace();  
            }  

        }  

解碼實現

  • 流程圖
Created with Raphaël 2.1.0開始讀壓縮文件,讀入文件頭,獲得字符總數,字符種數以及huffman表信息,重建huffman樹讀入正文,根據重建得到的huffman樹獲得原本的字符,將字符寫入解壓縮後的文件是否到達文件尾部?結束yesno
  • 數據結構
    HuffmanNode:huffman樹中的節點信息。
public class HuffmanNode {
    private int parent;//父節點
    private int lChild;//左子
    private int rChild;//右子
    private int weight;//權重
    }
  • 程序關鍵步驟

    • 重建Huffman樹。在文件頭中存放的原本就是Huffman樹的節點信息。

      in = new DataInputStream(new FileInputStream(file));  
          count = in.readInt();  
          charNum = in.readShort();  
          nodeNum = 2*charNum -1;  
          //rebuild the huffman tree  
          for(int i=0;i<charNum;i++){  
              HuffmanNode node = new HuffmanNode((char)in.readByte());  
              int parent = in.readShort();  
              node.setParent(parent);  
              tree.add(node);  
          }  
      
          for(int i=charNum;i<nodeNum;i++){  
              HuffmanNode node = new HuffmanNode(' ');  
              int l = in.readShort();  
              int r = in.readShort();  
              int p = in.readShort();  
              node.setlChild(l);  
              node.setrChild(r);  
              node.setParent(p);  
              tree.add(node);  
          }  
    • 解碼
      流程圖

Created with Raphaël 2.1.0開始Buff.length<32從文件中讀入整數將讀入的整數轉爲二進制字符串,並將其加到buff中根據buff中的01字符從頂向下遍歷huffman樹,得到葉子節點、其對應的解碼值,將其寫入文件,從buff中遍歷刪去已經遍歷過的字符字符數是否達到總數處理buff中剩餘內容結束yesnoyesno

代碼

while(true){  
            while(buff.length()<32){  
                temp = in.readInt();  
                String codeString = Integer.toBinaryString(temp);  
                while(codeString.length()<32){  
                    codeString='0'+codeString;  
                }  
                buff.append(codeString);  
            }  
            node = tree.get(tree.size()-1);  
            dep = 0;  
            while(!(node.getlChild()==-1&&node.getrChild()==-1)){  
                if(dep>=buff.length()){  
                    System.out.println( "Buff overflow");  
                }  
                if(buff.charAt(dep)=='0'){  
                    node = tree.get(node.getlChild());  
                }  
                else if(buff.charAt(dep)=='1'){  
                    node = tree.get(node.getrChild());  
                }  
                else{  
                    System.out.println("Coding error");  
                }  
                dep++;  
            }  

            char c = node.getCH();  
            num++;  
            if(num>=n/99){  
                System.out.print("=");  
                num=0;  
            }  
            count++;  
            if(count>=n){  
                break;  
            }  
            charBuff+=c;  
            if(charBuff.length()>256){  
                writer.write(charBuff);  
                charBuff="";  
            }  
            buff.delete(0, dep);  

        }  

    } catch(EOFException e){  
        //just do nothing  
    }  
    catch(Exception e){  
        e.printStackTrace();  
    } finally{  
        //there may be data released in the buff and charbuff, so we need to process them  
        while(buff.length()>0){  
            node = tree.get(tree.size()-1);  
            dep = 0;  
            while(!(node.getlChild()==-1&&node.getrChild()==-1)){  
                if(dep>=buff.length()){  
                    break;  
                }  
                if(buff.charAt(dep)=='0'){  
                    node = tree.get(node.getlChild());  
                }  
                else if(buff.charAt(dep)=='1'){  
                    node = tree.get(node.getrChild());  
                }  
                else{  
                    System.out.println("Coding error");  
                    //return;  
                }  
                dep++;  
            }  
            char c = node.getCH();  
            num++;  
            if(num>=n/99){  
                System.out.print("=");  
                num=0;  
            }  
            count++;  
            if(count>=n){  
                break;  
            }  
            charBuff+=c;  
            if(charBuff.length()>256){  
                try {  
                    writer.write(charBuff);  
                } catch (IOException e1) {  
                    // TODO Auto-generated catch block  
                    e1.printStackTrace();  
                }  
                charBuff="";  
            }  
            buff.delete(0, dep);  
        }  

        try {  
            writer.write(charBuff);  
            writer.close();  
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }   
    }  
    try{  
        writer.close();  
    } catch(IOException e){  
        throw e;  
    }  

項目源碼
留坑回頭放上

參考文章
http://blog.csdn.net/u010485034/article/details/30750245
http://blog.sina.com.cn/s/blog_694448320100nd5v.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章