1. 程式人生 > >專案:檔案的壓縮與解壓

專案:檔案的壓縮與解壓

檔案壓縮

開發平臺Visual Studio 2015

開發技術:堆排序,哈夫曼樹

專案描述


1.統計檔案中字元出現的次數,利用資料結構中的堆建造Huffman樹,字元出現次數多的編碼短,出現次數少的編碼長;
2.根據建造好的Huffman樹形成編碼,以對檔案進行壓縮;
3.將檔案中出現的字元以及他們出現的次數寫入配置檔案,以便後續的解壓縮;
4.根據配置檔案讀取相關資訊,重建Huffman樹,對壓縮後的檔案進行譯碼。

先看如下兩張圖,瞭解一點背景知識:

 

 圖一


圖二

哈弗曼樹的原理: 

     如果有一些結點的權值分別是1,2,3,4,5,6 ,構建出來的哈弗曼樹如下圖: 


      思想每次從陣列中取兩個當前權值最小的數去建立結點,並作為葉子結點,它們的根節點的權值是兩者之和,把它再放回陣列,第一次選擇1,2;第二次選擇3,3;第三次選擇4,5;~~~~ ~~

檔案壓縮的原理: 

檔案壓縮真正要用到的是哈夫曼編碼,對於上面那棵樹,它的哈弗曼編碼怎麼來的呢? 根據上圖,同理可得:


權值為1的結點的哈夫曼編碼就是1000 權值為2的結點的哈夫曼編碼就是1001 權值為3的結點的哈夫曼編碼就是101 權值為4的結點的哈夫曼編碼就是00 權值為5的結點的哈夫曼編碼就是01 權值為6的結點的哈夫曼編碼就是11 注意:哈夫曼編碼只是對葉子節點編碼

可以發現一個規律權值越小的,它的哈夫曼編碼越長,權值越大的,哈夫曼編碼越短。 那麼如何運用到檔案壓縮中呢? 

     假設有一個檔案的內容是“abbcccdddd“,‘a’出現的次數是1,‘b’出現的次數是2,‘c’出現的次數是3,‘d’出現的次數是4,以各字元出現的次數構建一個哈夫曼樹,併為各字元編碼,結果是: 

‘a’:100; ‘b’:101; ‘c’:11; ‘d’:0; 

如下圖:

 

以編碼的形式按照原字元的順序寫入壓縮檔案,如下: 1001011011111110000 

      這裡0或1代表一個二進位制位,那壓縮檔案是多大呢?壓縮檔案一共19個bit位,不夠的位補齊,只佔3個位元組 原檔案是多大呢?原字串“abbcccdddd“10個字元,佔10個位元組;這就是檔案壓縮的原理。 

專案主要思路

    1.統計:首先讀取一個檔案,統計出256個字元中各個字元出現的次數以及字元出現的總數;

    2.建樹:按照字元出現的次數,並以次數作為權值建立哈夫曼編碼樹建好樹後找出各個字元的編碼;

    3.壓縮:再次讀取檔案,按照該字元對應的編碼壓縮檔案;

    4.加工:將檔案的長度,檔案中各個字元以及它們出現的次數寫進配置檔案中;

    5.解壓:利用壓縮檔案和配置檔案恢復出原檔案;

    6.測試:首先觀察解壓的檔案和原檔案是否相同,再通過Beyond Compare 4軟體進行對比,驗證程式的正確性。

檔案壓縮的過程: 

首先要統計待壓縮檔案中各字元出現的次數,然後構造哈弗曼編碼,把編碼寫入壓縮檔案,不夠一個位元組的就在後面補零,因為要解壓縮,所以還得寫一個配置檔案,配置檔案裡面寫每個字元出現的次數。 

具體過程:

 a.讀取檔案,將每個字元,該字元出現的次數和權值構成哈夫曼樹;

b.哈夫曼樹是利用小堆構成,字元出現次數少的節點指標存在堆頂,出現次數多的在堆底;

c.每次取堆頂的兩個數,再將兩個數相加進堆,直到堆被取完,這時哈夫曼樹也建成;

         d.從哈夫曼樹中獲取哈夫曼編碼,然後再根據整個字元陣列來獲取出現了字元的編碼;

         e.獲取編碼後每次湊滿8位就將編碼串寫入到壓縮檔案;

         f.寫好配置檔案,統計每個字元及其出現次數,儲存到配置檔案中。

檔案解壓的過程: 

先去讀配置檔案,構建哈弗曼樹和哈弗曼編碼,用壓縮檔案裡的編碼去哈弗曼樹裡面找,找到葉子結點,就把葉子節點的字元寫入解壓縮檔案中。 

具體過程:

 a.讀取配置檔案,統計所有字元的個數;

 b.構建哈夫曼樹,讀解壓縮檔案,將所讀到的編碼字元的這個節點所含的字元寫入到解壓縮檔案中,直到將壓縮檔案讀完;

 c.解壓縮完成之後利用Beyond Compare 4軟體,進行檔案的測試。

專案測試:

       通過Beyond Compare 4軟體,對檔案壓縮前和壓縮後的內容進行對比,驗證程式的正確性。 效能測試,在release版本下會更高效一些,時間會縮短很多因為 Debug 通常稱為除錯版本,它包含除錯資訊,並且不作任何優化,便於程式設計師除錯程式,也就是博主程式設計用的版本;Release 稱為釋出版本,它往往是進行了各種優化,使得程式在程式碼大小和執行速度上都是最優的,以便使用者很好地使用。

執行結果:

 

測試用例

      原檔案為“穿過落雁修竹,看過月升日落你說總有一日會名揚天下實現你抱負”(名為1.txt),經過壓縮(1.huff),解壓後(1_Com.txt)的檔案的內容如下:

 

圖三

 

圖四

專案中出現的問題

1測試的時候發現如果待壓縮檔案中出現中文,程式就會崩潰,將錯誤定位到構造哈弗曼編碼的函式處,通過單步除錯發現是陣列越界所致,因為如果只是字元,它的範圍是-128~127,程式中通過一個char型別的變數作為陣列的下標(0~127),是沒有問題的,但漢字的編碼是兩個位元組(只能收錄2萬多的漢字,但在本專案中已經夠用了,這裡不進行深究),所以會發生陣列越界,解決方法是將char強轉為unsigned char可表示範圍為0~255

2為什麼要使用配置檔案?

在專案中,將字元對應的編碼轉化為位,在unsigned char中填充位,填滿後就寫入到壓縮檔案中。

         問題1:最後一個位元組是不是很有可能沒有填滿,該如何判斷他是否填滿以及填了幾個字元的編碼?

         問題2:若依次壓縮一些檔案,壓縮完後再去解壓,那麼編碼此時已經沒有了,該如何解壓?

        上面的兩個問題通過配置檔案解決,假如要壓縮的檔案叫xxx,那麼可生成一個xxx.config的配置檔案,在該配置檔案中寫入<檔案的總長度>(恢復時知道應該恢復多少個字元),<char-times>(字元以及出現的次數,用於解壓時重建哈夫曼樹),利用配置檔案即可解決這兩個問題

3在檔案恢復的時候需要注意哪些問題?

     有些特殊字元的處理需要注意一下,比如 '\n',我的程式中有一個函式就是讀取一行字元,但是若是該字元本身就是'\n'呢,我們寫入的<char-times>的char就是\n呢?那麼就比較棘手了,對於這個問題,千萬不能漏掉,否則就不能恢復出原來的檔案。讀取配置檔案的時候若讀到’\n‘,則說明該字元就是'\n',應該繼續讀取它的次數。

有問題可微信掃碼諮詢: