數據流壓縮原理和數據壓縮Zlib的實現

1. 壓縮原理deflate算法

壓縮的本質就是去冗余,去除信息冗余,使用最短的編碼保存最完整的數據信息。所以對于不同的場景,壓縮采用的算法也因時制宜,比如視頻和圖片可以采用有損壓縮,而文本數據采用無損壓縮。壓縮率又取決于信息的冗余度,也就是內容中重復的比例。那些均勻分布的隨機字符串,壓縮率會降到最低,即香農限

1.1 Deflate壓縮算法

deflate的概念

deflate是zip文件的默認算法。它更是一種數據流壓縮算法。

deflate的三種壓縮模型
  • 不壓縮數據
  • 壓縮,先用LZ77,再用huffman編碼。壓縮樹是被Deflate規范定義的,不需要額外的空間存儲這個樹
  • 壓縮,采用LZ,然后使用huffman算法。壓縮樹是壓縮器生成的,并與數據一起存儲。
數據被分割成不同的塊,每一個不同的塊使用的單一的壓縮模式。

1.2. LZ77算法原理

概念

LZ77壓縮算法采用字典的方式進行壓縮,是一種簡單但是很高效的數據壓縮算法。其方式就是把數據中一些可以組織成短語的字符加入字典。維護三個概念:短語字典、滑動窗口、向前緩沖區

  • 前向緩沖區:數據預載入
  • 滑動窗口:一個定長的用于處理當前數據的窗口
  • 短語字典:用窗口去更新字典,并去匹配前向緩沖區的數據
    算法邏輯:先通過前向緩沖區預讀數據,然后再向滑動窗口移入,不斷的尋找能與字典中短語匹配的最長短語,然后通過標記符標記
LZ77壓縮
  • 壓縮數據的時候,前向緩沖區和滑動窗口之間做短語匹配會存在兩種情況,找不到匹配時,將未匹配的符號編碼成符號標記,反之編碼即可。
  • 短語標記包含滑動窗口的偏移量(匹配開始的地方計算)、匹配中的符號個數、匹配結束后的前向緩沖區中的第一個符號
  • 匹配步驟:
  1. 尋找當前窗口內字符串在前向緩沖區中有沒有匹配項,沒有繼續推進窗口和預讀區
  2. 如果有匹配項,使用三元組標記當前字符串,并向前移動匹配的符號個數+1位
  3. 繼續在窗口中尋找匹配項(注意滑動窗口中尋找匹配字串,預設窗口中尋找截止位),回到1
  4. 重復直至匹配完畢
LZ77解壓

壓縮的逆過程,通過解碼標記和保持滑動窗口中的符號來更新解壓數據。當解碼字符被標記:將標記編碼成字符拷貝到滑動窗口中,一步一步直到全部翻譯完成

  • 優缺點:大多數情況下壓縮比比較高,和你選擇的各種參數配置以及數據的熵有關。壓縮過程較為耗時,解壓過程較快。
deflate采用的改進版LZ77算法
  • 三個字節以上的重復串才進行編碼。

為什么是三個字節?
這是由于,gzip中 <len,offset>標記的長度是23位長,如果重復長度小于這個值則壓縮失效

  • 原始數據被轉換成長度距離標記后,再由huffman編碼表示。這過程中,首先生成編碼表(Huffman編碼的對應關系),具體壓縮大小在于精細分配結構體的位域。
  • 解壓:讀取二進制文件,構建huffman表,根據表生成字符串,用LZ77解碼。

1.3. Huffman算法原理

前綴碼

在流式傳輸中,不定長編碼數據的解碼想要保持唯一性,必須滿足唯一可以碼的條件。而異前綴碼就是一種唯一可譯碼的候選,當然這樣會增加編碼的長度,卻可以簡化解碼。

哈夫曼編碼

huffman編碼是一種基于概率分布的貪心策略最優前綴碼。huffman編碼可以有效的壓縮數據,壓縮率取決于數據本身的信息冗余度

策略

計算數據中各符號出現的概率,根據概率從小到大,從下往上反向構建構造碼樹,這樣最終得到的編碼的平均長度是最短的。同時也是唯一可譯的

1.4. huffman算法實例測試

算法舉例
image.png

解讀:在一開始,每一個字符已經按照出現概率的大小排好順序,在后續的步驟中,每一次將概率最低的兩棵樹合并,然后用合并后的結果再次排序(為了找出最小的兩棵樹)。在gzip源碼中并沒有專門去排序,而是使用專門的數據結構(比如最小堆或者紅黑樹)。

編碼實現

使用優先隊列實現huffman樹,最后基于Huffman樹最終實現文件壓縮。
具體步驟:

  • 統計文件中符號個數
  • 持久化文件字符信息(個數)
  • 壓縮
  • 解壓縮

2.gzip的格式分析

gzip = gzip 頭 + deflate 編碼的實際內容 + gzip 尾

3. zlib庫函數API分析

zlib = zlib 頭 + deflate 編碼的實際內容 + zlib 尾

zlib庫API分析

基本數據結構
typedef struct gz_header_s{
  int text;/* true if compressed data believed to be text*/
  uLong time; /* modification time*/
  int xflags;/* extra flags*/
  int os; /* operating system*/
  Bytef *extra;/* pointer to extra field or Z_NULL if none*/
  uInt extra_len;/* extra field length*/
  uInt extra_max;
  Bytef *name;
  uInt name_max;
  Bytef *comment;
  uInt comm_max;
  int hcrc;
  int done;
}

壓縮之前:初始化各種輸入輸出緩沖區;
壓縮:我們可以不斷往這些緩沖區中填充內容,然后由deflate函數進行壓縮或者indeflate函數進行解壓

  • deflate盡可能的壓縮數據,當輸入緩沖區為空或者輸出緩沖區滿了的時候會停止,它會帶來輸出延遲,除非強行刷新緩沖區
  • deflate的語義如下:
  1. 從next_in開始壓縮數據從而更新next_in和avail_in.如果不是所有數據都可以被處理,next_in和avail_in會更新,當再次調用deflate函數的時候會從這一點繼續處理
  2. 從next_out開始提供更多輸出數據從而更新next_out和avail_out,如果flush參數不為零,則這個動作是強制性的,經常性的刷新會降低壓縮比例,所以只有必要的時候才會設置這個參數。


    image.png

總結:在調用deflate函數之前,應用程序必須保證至少一個動作被執行(avail_in或者avail_out被設置),用提供更多數據或者消耗更多的數據的方式。avail_out在函數調用之前千萬不能為零。應用程序可以隨時消耗被壓縮的輸出數據

4. zlib庫實戰(壓縮文件和解壓文件)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容