摘自:http://blog.csdn.net/ghevinn/article/details/45747465 算法
gzip 所使用壓縮算法的基本原理
gzip 對於要壓縮的文件,首先使用LZ77算法的一個變種進行壓縮,對獲得的結果再使用Huffman編碼的方法(實際上gzip根據狀況,選擇使用靜態Huffman編碼或者動態Huffman編碼,詳細內容在實現中說明)進行壓縮。因此明白了LZ77算法和Huffman編碼的壓縮原理,也就明白了gzip的壓縮原理。咱們來對LZ77算法和Huffman編碼作一個簡單介紹。
1.1 LZ77算法簡介
這一算法是由Jacob Ziv 和 Abraham Lempel 於 1977 年提出,因此命名爲 LZ77。
1.1.1 LZ77算法的壓縮原理
若是文件中有兩塊內容相同的話,那麼只要知道前一塊的位置和大小,咱們就能夠肯定後一塊的內容。因此咱們能夠用(二者之間的距離,相同內容的長度)這樣一對信息,來替換後一塊內容。因爲(二者之間的距離,相同內容的長度)這一對信息的大小,小於被替換內容的大小,因此文件獲得了壓縮。
下面咱們來舉一個例子。
有一個文件的內容以下
http://jiurl.yeah.net http://jiurl.nease.net
其中有些部分的內容,前面已經出現過了,下面用()括起來的部分就是相同的部分。
http://jiurl.yeah.net (http://jiurl.)nease(.net)
咱們使用 (二者之間的距離,相同內容的長度) 這樣一對信息,來替換後一塊內容。
http://jiurl.yeah.net (22,13)nease(23,4)
(22,13)中,22爲相同內容塊與當前位置之間的距離,13爲相同內容的長度。
(23,4)中,23爲相同內容塊與當前位置之間的距離,4爲相同內容的長度。
因爲(二者之間的距離,相同內容的長度)這一對信息的大小,小於被替換內容的大小,因此文件獲得了壓縮。
1.1.2 LZ77使用滑動窗口尋找匹配串
LZ77算法使用"滑動窗口"的方法,來尋找文件中的相同部分,也就是匹配串。咱們先對這裏的串作一個說明,它是指一個任意字節的序列,而不只僅是能夠在文本文件中顯示出來的那些字節的序列。這裏的串強調的是它在文件中的位置,它的長度隨着匹配的狀況而變化。
LZ77從文件的開始處開始,一個字節一個字節的向後進行處理。一個固定大小的窗口(在當前處理字節以前,而且緊挨着當前處理字節),隨着處理的字節不斷的向後滑動,就象在陽光下,飛機的影子滑過大地同樣。對於文件中的每一個字節,用當前處理字節開始的串,和窗口中的每一個串進行匹配,尋找最長的匹配串。窗口中的每一個串指,窗口中每一個字節開始的串。若是當前處理字節開始的串在窗口中有匹配串,就用(之間的距離,匹配長度) 這樣一對信息,來替換當前串,而後從剛纔處理完的串以後的下一個字節,繼續處理。若是當前處理字節開始的串在窗口中沒有匹配串,就不作改動的輸出當前處理字節。
處理文件中第一個字節的時候,窗口在當前處理字節以前,也就是尚未滑到文件上,這時窗口中沒有任何內容,被處理的字節就會不作改動的輸出。隨着處理的不斷向後,窗口愈來愈多的滑入文件,最後整個窗口滑入文件,而後整個窗口在文件上向後滑動,直到整個文件結束。
1.1.3 使用LZ77算法進行壓縮和解壓縮
爲了在解壓縮時,能夠區分「沒有匹配的字節」和「(之間的距離,匹配長度)對」,咱們還須要在每一個「沒有匹配的字節」或者「(之間的距離,匹配長度)對」以前,放上一位,來指明是「沒有匹配的字節」,仍是「(之間的距離,匹配長度)對」。咱們用0表示「沒有匹配的字節」,用1表示「(之間的距離,匹配長度)對」。
實際中,咱們將固定(之間的距離,匹配長度)對中的,「之間的距離」和「匹配長度」所使用的位數。因爲咱們要固定「之間的距離」所使用的位數,因此咱們才使用了固定大小的窗口,好比窗口的大小爲32KB,那麼用15位(2^15=32K)就能夠保存0-32K範圍的任何一個值。實際中,咱們還將限定最大的匹配長度,這樣一來,「匹配長度」所使用的位數也就固定了。
實際中,咱們還將設定一個最小匹配長度,只有當兩個串的匹配長度大於最小匹配長度時,咱們才認爲是一個匹配。咱們舉一個例子來講明這樣作的緣由。好比,「距離」使用15位,「長度」使用8位,那麼「(之間的距離,匹配長度)對」將使用23位,也就是差1位3個字節。若是匹配長度小於3個字節的話,那麼用「(之間的距離,匹配長度)對」進行替換的話,不但沒有壓縮,反而會增大,因此須要一個最小匹配長度。
壓縮:
從文件的開始到文件結束,一個字節一個字節的向後進行處理。用當前處理字節開始的串,和滑動窗口中的每一個串進行匹配,尋找最長的匹配串。若是當前處理字節開始的串在窗口中有匹配串,就先輸出一個標誌位,代表下面是一個(之間的距離,匹配長度) 對,而後輸出(之間的距離,匹配長度) 對,而後從剛纔處理完的串以後的下一個字節,繼續處理。若是當前處理字節開始的串在窗口中沒有匹配串,就先輸出一個標誌位,代表下面是一個沒有改動的字節,而後不作改動的輸出當前處理字節,而後繼續處理當前處理字節的下一個字節。
解壓縮:
從文件開始到文件結束,每次先讀一位標誌位,經過這個標誌位來判斷下面是一個(之間的距離,匹配長度) 對,仍是一個沒有改動的字節。若是是一個(之間的距離,匹配長度)對,就讀出固定位數的(之間的距離,匹配長度)對,而後根據對中的信息,將匹配串輸出到當前位置。若是是一個沒有改動的字節,就讀出一個字節,而後輸出這個字節。
咱們能夠看到,LZ77壓縮時須要作大量的匹配工做,而解壓縮時須要作的工做不多,也就是說解壓縮相對於壓縮將快的多。這對於須要進行一次壓縮,屢次解壓縮的狀況,是一個巨大的優勢。
1.2 Huffman編碼簡介
1.2.1 Huffman編碼的壓縮原理
咱們把文件中必定位長的值看做是符號,好比把8位長的256種值,也就是字節的256種值看做是符號。咱們根據這些符號在文件中出現的頻率,對這些符號從新編碼。對於出現次數很是多的,咱們用較少的位來表示,對於出現次數很是少的,咱們用較多的位來表示。這樣一來,文件的一些部分位數變少了,一些部分位數變多了,因爲變小的部分比變大的部分多,因此整個文件的大小仍是會減少,因此文件獲得了壓縮。
1.2.2 Huffman編碼使用Huffman樹來產生編碼
要進行Huffman編碼,首先要把整個文件讀一遍,在讀的過程當中,統計每一個符號(咱們把字節的256種值看做是256種符號)的出現次數。而後根據符號的出現次數,創建Huffman樹,經過Huffman樹獲得每一個符號的新的編碼。對於文件中出現次數較多的符號,它的Huffman編碼的位數比較少。對於文件中出現次數較少的符號,它的Huffman編碼的位數比較多。而後把文件中的每一個字節替換成他們新的編碼。
創建Huffman樹:
把全部符號當作是一個結點,而且該結點的值爲它的出現次數。進一步把這些結點當作是隻有一個結點的樹。
每次從全部樹中找出值最小的兩個樹,爲這兩個樹創建一個父結點,而後這兩個樹和它們的父結點組成一個新的樹,這個新的樹的值爲它的兩個子樹的值的和。如此往復,直到最後全部的樹變成了一棵樹。咱們就獲得了一棵Huffman樹。
經過Huffman樹獲得Huffman編碼:
這棵Huffman樹,是一棵二叉樹,它的全部葉子結點就是全部的符號,它的中間結點是在產生Huffman樹的過程當中不斷創建的。咱們在Huffman樹的全部父結點到它的左子結點的路徑上標上0,右子結點的路徑上標上1。
如今咱們從根節點開始,到全部葉子結點的路徑,就是一個0和1的序列。咱們用根結點到一個葉子結點路徑上的0和1的序列,做爲這個葉子結點的Huffman編碼。
咱們來看一個例子。
有一個文件的內容以下
abbbbccccddde
咱們統計一下各個符號的出現次數,
a b c d e
1 4 4 3 1
創建Huffman樹的過程如圖:編碼
經過最終的Huffman樹,咱們能夠獲得每一個符號的Huffman編碼。
a 爲 110
b 爲 00
c 爲 01
d 爲 10
e 爲 111
咱們能夠看到,Huffman樹的創建方法就保證了,出現次數多的符號,獲得的Huffman編碼位數少,出現次數少的符號,獲得的Huffman編碼位數多。
各個符號的Huffman編碼的長度不一,也就是變長編碼。對於變長編碼,可能會遇到一個問題,就是從新編碼的文件中可能會沒法如區分這些編碼。
好比,a的編碼爲000,b的編碼爲0001,c的編碼爲1,那麼當遇到0001時,就不知道0001表明ac,仍是表明b。出現這種問題的緣由是a的編碼是b的編碼的前綴。
因爲Huffman編碼爲根結點到葉子結點路徑上的0和1的序列,而一個葉子結點的路徑不多是另外一個葉子結點路徑的前綴,因此一個Huffman編碼不可能爲另外一個Huffman編碼的前綴,這就保證了Huffman編碼是能夠區分的。
1.2.3 使用Huffman編碼進行壓縮和解壓縮
爲了在解壓縮的時候,獲得壓縮時所使用的Huffman樹,咱們須要在壓縮文件中,保存樹的信息,也就是保存每一個符號的出現次數的信息。
壓縮:
讀文件,統計每一個符號的出現次數。根據每一個符號的出現次數,創建Huffman樹,獲得每一個符號的Huffman編碼。將每一個符號的出現次數的信息保存在壓縮文件中,將文件中的每一個符號替換成它的Huffman編碼,並輸出。
解壓縮:
獲得保存在壓縮文件中的,每一個符號的出現次數的信息。根據每一個符號的出現次數,創建Huffman樹,獲得每一個符號的Huffman編碼。將壓縮文件中的每一個Huffman編碼替換成它對應的符號,並輸出。url