1. 壓縮原理deflate算法
壓縮的本質就是去冗余,去除信息冗余,使用最短的編碼保存最完整的數據信息。所以對于不同的場景,壓縮采用的算法也因時制宜,比如視頻和圖片可以采用有損壓縮,而文本數據采用無損壓縮。壓縮率又取決于信息的冗余度,也就是內容中重復的比例。那些均勻分布的隨機字符串,壓縮率會降到最低,即香農限
1.1 Deflate壓縮算法
deflate的概念
deflate是zip文件的默認算法。它更是一種數據流壓縮算法。
deflate的三種壓縮模型
- 不壓縮數據
- 壓縮,先用LZ77,再用huffman編碼。壓縮樹是被Deflate規范定義的,不需要額外的空間存儲這個樹
- 壓縮,采用LZ,然后使用huffman算法。壓縮樹是壓縮器生成的,并與數據一起存儲。
數據被分割成不同的塊,每一個不同的塊使用的單一的壓縮模式。
1.2. LZ77算法原理
概念
LZ77壓縮算法采用字典的方式進行壓縮,是一種簡單但是很高效的數據壓縮算法。其方式就是把數據中一些可以組織成短語的字符加入字典。維護三個概念:短語字典、滑動窗口、向前緩沖區
- 前向緩沖區:數據預載入
- 滑動窗口:一個定長的用于處理當前數據的窗口
- 短語字典:用窗口去更新字典,并去匹配前向緩沖區的數據
算法邏輯:先通過前向緩沖區預讀數據,然后再向滑動窗口移入,不斷的尋找能與字典中短語匹配的最長短語,然后通過標記符標記
LZ77壓縮
- 壓縮數據的時候,前向緩沖區和滑動窗口之間做短語匹配會存在兩種情況,找不到匹配時,將未匹配的符號編碼成符號標記,反之編碼即可。
- 短語標記包含滑動窗口的偏移量(匹配開始的地方計算)、匹配中的符號個數、匹配結束后的前向緩沖區中的第一個符號
- 匹配步驟:
- 尋找當前窗口內字符串在前向緩沖區中有沒有匹配項,沒有繼續推進窗口和預讀區
- 如果有匹配項,使用三元組標記當前字符串,并向前移動匹配的符號個數+1位
- 繼續在窗口中尋找匹配項(注意滑動窗口中尋找匹配字串,預設窗口中尋找截止位),回到1
- 重復直至匹配完畢
LZ77解壓
壓縮的逆過程,通過解碼標記和保持滑動窗口中的符號來更新解壓數據。當解碼字符被標記:將標記編碼成字符拷貝到滑動窗口中,一步一步直到全部翻譯完成
- 優缺點:大多數情況下壓縮比比較高,和你選擇的各種參數配置以及數據的熵有關。壓縮過程較為耗時,解壓過程較快。
deflate采用的改進版LZ77算法
- 三個字節以上的重復串才進行編碼。
為什么是三個字節?
這是由于,gzip中 <len,offset>標記的長度是23位長,如果重復長度小于這個值則壓縮失效
- 原始數據被轉換成長度距離標記后,再由huffman編碼表示。這過程中,首先生成編碼表(Huffman編碼的對應關系),具體壓縮大小在于精細分配結構體的位域。
- 解壓:讀取二進制文件,構建huffman表,根據表生成字符串,用LZ77解碼。
1.3. Huffman算法原理
前綴碼
在流式傳輸中,不定長編碼數據的解碼想要保持唯一性,必須滿足唯一可以碼的條件。而異前綴碼就是一種唯一可譯碼的候選,當然這樣會增加編碼的長度,卻可以簡化解碼。
哈夫曼編碼
huffman編碼是一種基于概率分布的貪心策略最優前綴碼。huffman編碼可以有效的壓縮數據,壓縮率取決于數據本身的信息冗余度
策略
計算數據中各符號出現的概率,根據概率從小到大,從下往上反向構建構造碼樹,這樣最終得到的編碼的平均長度是最短的。同時也是唯一可譯的
1.4. huffman算法實例測試
算法舉例
解讀:在一開始,每一個字符已經按照出現概率的大小排好順序,在后續的步驟中,每一次將概率最低的兩棵樹合并,然后用合并后的結果再次排序(為了找出最小的兩棵樹)。在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的語義如下:
- 從next_in開始壓縮數據從而更新next_in和avail_in.如果不是所有數據都可以被處理,next_in和avail_in會更新,當再次調用deflate函數的時候會從這一點繼續處理
-
從next_out開始提供更多輸出數據從而更新next_out和avail_out,如果flush參數不為零,則這個動作是強制性的,經常性的刷新會降低壓縮比例,所以只有必要的時候才會設置這個參數。
image.png
總結:在調用deflate函數之前,應用程序必須保證至少一個動作被執行(avail_in或者avail_out被設置),用提供更多數據或者消耗更多的數據的方式。avail_out在函數調用之前千萬不能為零。應用程序可以隨時消耗被壓縮的輸出數據