1. 程式人生 > >基於哈夫曼壓縮演算法的壓縮與解壓實現(Java)

基於哈夫曼壓縮演算法的壓縮與解壓實現(Java)

如果需要對一個檔案進行壓縮,第一步是提取出檔案中的內容資訊;第二步是對其中的字元等進行統計,獲得壓縮編碼;第三步是把壓縮編碼及解壓檔案需要的資訊存入一個檔案(或者生成新檔案並存入)。這樣對檔案的壓縮就完成了。

下面就是檔案的解壓,第一步還是讀入檔案,即從前面生成的含有壓縮編碼及解壓資訊的檔案中讀取資訊內容;第二步是整理出對應的字元編碼資訊;第三步是按照第二步還原出來的解碼資訊將檔案還原。這樣就完成了一個檔案的編碼壓縮和解壓。

接下來介紹哈夫曼壓縮演算法,這個演算法適合壓縮具有較高資訊重複率的檔案,比如一個檔案的內容是:“aaaaaaaaaaaaaaabbbbbbbbbbbbbbbabababab….”。對於這串字元,所包含的不同字元個數較少,且同一個字元的重複率較高,經過哈夫曼編碼壓縮後可以大大的節省空間。而對於其他的檔案,比如讀取出來是“qwertyuiopasdfghjklzxcvbnm…..”。這一串字元所包含的不同的字元個數較多而且重複率較低,如果運用哈夫曼壓縮演算法進行壓縮,就會得到一個比原檔案大幾倍的“壓縮檔案”,所以還不如不壓縮。

具體壓縮演算法如下:首先從檔案中讀取資訊,我們假定是一串字元;接下來統計這一串字元中不重複的字元有哪些及其出現的次數(權重);然後根據字元的權重建立哈夫曼二叉樹;再接著對每一個字元編碼,得到唯一的‘0100…..’這樣的01編碼表示;然後用每一個字元的01編碼替換原字串中出現的該字元(這樣看起來似乎01編碼資訊比原本的字串長度還要長);所以接下來還需要對這一串01編碼進行處理,最終將處理完的編碼資訊和解碼所需的字元與其唯一的01編碼資訊存入同一個檔案,當然怎麼存是根據寫程式的人打算如何解壓來決定的,如果不知道存入資訊的順序,即使拿到壓縮檔案也很難解壓。

前面還有兩個問題沒有解決:1、如何根據哈夫曼二叉樹對字元進行編碼,2、如何對比原字串還要長的01編碼進行處理。請看下圖:
這裡寫圖片描述


假設一串字元裡面不重複的字元僅有:a,b,c,d,e,且各個字元出現的次數(權重)為:1,3,5,6,12,那麼按照權重建立哈夫曼二叉樹,就得到每一個字元的唯一位置,下面對字元進行編碼:從根節點開始,編碼String code=”“;往左就code+=’0’,往右就code+=’1’;這樣直到找到對應的字元,停止編碼並得到唯一的01編碼code,比如上圖的‘a’經過上述步驟編碼就得到‘1100‘。

接著使用上面的例子,加入一串字元為“aabbcde”,替換為對應的01編碼就得到“1100110011011101111100”,很明顯這串01編碼比原來的“aabb。。”要長,所以接下來就對這一串01編碼進行處理:將其劃分為8個一組,上面的經過劃分後就得到:“11001100”,“11011101”,“111100”,我們可以看到大多數情況下經過劃分後都會在最後產生不足8位的情況,這時候可以採用在最後補0的方法進行補全,上面的經過補0後得到“11001100”,“11011101”“11110000”,然後將8位的01轉為對應的數字並存入陣列中,經過這一步後我們得到三個數字,在存入檔案時在吧數字轉為byte型,這樣一來在檔案中就只需要3個位元組就可以把上面的“aabb….”給存完,明顯節約了空間,也就實現了壓縮。

好了就檔案的壓縮而言,到這裡好像沒什麼問題,但我們壓縮檔案只是為了節省儲存空間,最終還需要解壓出來,如果沒有存入解壓資訊就無法完成解壓,那麼經過壓縮獲得的檔案就毫無意義。所以我們還需要存入一下額外的資訊:字元對應的編碼:(a:1100)(b:1101)(c:111)(d:10)(e:0)、沒有補0時01編碼的長度或補0的個數,因為我們需要把補的0去掉、以及由01碼得到的陣列資料。這樣一個可以解壓的壓縮檔案就完成了。

下面再接著聊如何解壓,首先需要從待解壓檔案中讀取資訊;接著還原出編碼對應的字元:(a:1100)(b:1101)(c:111)(d:10)(e:0)、沒有補0時01編碼的長度或補0的個數、以及由01碼得到的陣列資料。;然後根據把陣列資料還原為01編碼並去掉末尾補加的0,;再然後從01碼的開始一個個與字元編碼進行比對,還原出原本的字串;最後將還原出來的字串存入檔案就完成了檔案的解壓。

下面是我用Java實現的檔案壓縮和解壓:
這裡寫圖片描述
上面是原檔案的內容,下面是經過壓縮,從壓縮檔案中解壓出來的檔案內容,經過比對內容一致。
這裡寫圖片描述
這裡上面一個檔案的內容是完整的壓縮檔案(包含解壓資訊),下面一個是僅有01對應的陣列資料資訊。這裡原檔案大小是2.19KB,解壓還原出來的檔案也是2.19KB,包含完整壓縮資訊的檔案為1.63KB,僅包含壓縮後的編碼資訊的檔案為1.18KB.這裡也印證了前面所說的哈夫曼演算法適合壓縮具有較高重複率的檔案,對重複率不高的檔案壓縮比不高,效果較差。

接下來給大家分享一下我寫的實現哈夫曼壓縮的Java程式,大家感興趣的可以在這裡下載原始碼:
https://pan.baidu.com/s/1T3258XDwf72IJJ499ERYMw#list/path=%2F
雖然我寫了較多的註釋,但呼叫類裡面的函式還是比較麻煩,並且有時不記得該呼叫哪個函數了,所以就對每個類加了一個對外的介面函式,只要知道哪個類實現什麼功能就行,然後傳入對應的資料,並獲得處理後的資訊。

最後我的程式在使用時的呼叫介面如下:

import java.io.File;

public class Main {

    //最終總函式,其他函式也可以呼叫壓縮及解壓的相關類完成功能
    public void toMain(){
        //定義輸入(待壓縮)檔案
        File from=new File("C:\\Users\\user\\Desktop\\a.txt");
        comFILEIN comfilein=new comFILEIN();
        //從待壓縮檔案中讀取有效字串
        String str=comfilein.getfilestr(from);
        //根據讀取的有效字串,完成壓縮,獲得有關資訊並儲存在conpress例項化的物件中
        compress newcom=new compress();
        newcom.tocompress(str);
        //從儲存壓縮資訊的物件中輸出到新建檔案中
        comFILEOUT comfileout=new comFILEOUT();
        comfileout.outtofile(newcom);
        //待解壓檔案
        File in=new File("C:\\Users\\user\\Desktop\\outa.txt");
        //從待解壓檔案中讀取壓縮資訊,儲存在解壓uncompress類的例項化物件中
        uncomFILEIN unfilein=new uncomFILEIN();
        unfilein.unfiletouncompress(in);
        uncompress uncom=new uncompress();
        uncom.touncompres(unfilein.mapchtobi, unfilein.mapbitoch, unfilein.d, unfilein.savenum);
        //將解壓還原的資訊放在新建檔案中
        uncomFILEOUT uncomout=new uncomFILEOUT();
        uncomout.uncomtofile(uncom);
    }
    public static void main(String[] args){
        Main M=new Main();
        M.toMain();
    }
}

壓縮和解壓是兩個不同的功能,所以寫不同的類進行實現,對檔案進行壓縮時需要讀入檔案,對檔案資訊進行處理,寫出壓縮檔案,所以在壓縮的部分又分別寫了檔案讀入和寫出功能的類,對解壓時也同樣分別寫了檔案讀入和寫出的類。所有類的呼叫都通過對外介面進行資訊傳入和傳出。