簡單聊聊GZIP 的壓縮原理與日常應用

簡單聊聊GZIP 的壓縮原理與日常應用

前言

在基於HTTP 協議的網絡傳輸中GZip 經常被使用,Nginx 中也可以使用半行代碼開啟GZip。 GZip 壓縮的原理是什麼呢?本篇文章是我在網上閱讀了一些文檔後做的簡單總結。

從RFC 1952看起

RFC 1952GZIP file format specification version 4.3 。該規範主要定義了GZip 壓縮的在數據格式方面的規範,以方便不同的操作系統、CPU、文件系統等之間進行文件傳輸交換。下面挑有意思的幾個點說,感興趣的可以閱讀RFC 1952 的原文。

GZIP 的文件格式在設計上其實是可以允許一個文件裡有多個壓縮數據集(compressed data sets)—— GZIP 壓縮後的片段拼接而成的。但就我們大多數應用場景來說,基本上都是一個文件一個壓縮數據集,如果是多個文件一起打包的話,也往往是將多個包合併成一個tar 文件。

每個壓縮數據集都是下面的結構:

| ID1 | ID2 | CM | FLG | MTIME(4字節) | XFL | OS | ---> more

||之間是1 byte,都是大端字節(Big Edian)

  • 其中ID1 和ID2 分別是0x1f 和0x8b,用來標識文件格式是gzip
  • CM標識加密算法,目前0-7是保留字,8指的是deflate算法
  • FLG 從低地址到高地址分別是FTEXT、FHCRC、FEXTRA、FNAME、FCOMMENT、reserved、 reserved、reserved,這裡每個bit 被設置了之後有什麼意義感興趣的話可以詳細參考RFC 1952。比較有意思的是FEXTRA,如果它被設置了表示存在額外的拓展字段。拓展字段的結構如下:
  • | SI1 | SI2 | LEN | ... LEN bytes of subfield data ... |
  • SI1、SI2 是對子域的ID,由ASCII 碼組成。如果你需要使用的話,可以向他的維護者Jean-Loup Gailly <[email protected]>發郵件申請。目前Apollo file 就有自己的專屬ID
  • MTIME 指的是源文件最近一次修改時間,存的是Unix 時間戳
  • XFL 是給壓縮算法傳的一些參數,用來標識如何解壓。 defalte 算法中2 表示使用壓縮率最高的算法,4 表示使用壓縮速度最快的算法
  • OS 標識壓縮程序運行的文件系統,以處理EOF 等的問題
  • more 後面是根據FLG 的開啟情況決定的,可能會有循環冗餘校驗碼、源文件長度、附加訊息等多種其他訊息

壓縮核心之Deflate

GZIP的核心是Deflate,在RFC 1951中被標準化,並且在當時作為LZW的替代品有了非常廣泛的使用。

Deflate 是一個同時使用LZ77 與Huffman Coding 的算法,這裡簡單介紹下這兩種算法的大致思路:

LZ77

LZ77的核心思路是如果一個串中有兩個重複的串,那麼只需要知道第一個串的內容和後面串相對於第一個串起始位置的距離+串的長度

比如: ABCDEFGABCDEFH → ABCDEFG(7,6)H。 7 指的是往前第7 個數開始,6 指的是重複串的長度,ABCDEFG(7,6)H 完全可以表示前面的串,並且是沒有二義性的。

LZ77 用滑動窗口(sliding-window compression)來實現這個算法。具體思路是掃描頭從串的頭部開始掃描串,在掃描頭的前面有一個長度為N 的滑動窗口。如果發現掃描頭處的串和窗口裡的最長匹配串是相同的,則用(兩個串之間的距離,串的長度)來代替後一個重複的串,同時還需要添加一個表示是真實串還是替換後的“串”的字節在前面以方便解壓(此串需要在真實串和替換“串” 之前都有存在)。

實際過程中滑動窗口的大小是固定的,匹配的串也有最小長度限制,以方便標識+兩個串之間的距離+串的長度所佔用的字節是固定的以及不要約壓縮體積越大。更加詳細的實現可以參考: Standford Edu. lz77 algorithmLZ77 Compression AlgorithmLZ77壓縮算法編碼原理詳解(結合圖片和簡單代碼)

這里通過這個壓縮機制也就能比較容易的解釋為啥CSS BEM寫法GZIP壓縮之後可以忽略長度以及JPEG圖片GZIP之後可能會變大的情況了

解壓:GZIP 的壓縮因為要在窗口裡尋找重複串相對來說效率是比較低的(LZ77 還是通過Hash 等系列方法提高了很多),那解壓又是怎麼個情況呢?觀察壓縮後的整個串,每個小串前都有一個標識要標記是原始串還是替換“串”,通過這個標識就能以O(1)的複雜度直接讀完並且替換完替換“串”,整體上效率是非常可觀的。

Huffman Coding

Huffman Coding 是大學課本中一般都會提到的算法。核心思路是通過構造Huffman Tree 的方式給字符重新編碼(核心是避免一個葉子的路徑是另外一個葉子路徑的前綴),以保證出現頻路越高的字符佔用的字節越少。關於Huffman Tree的構造這裡不再細說,不太清楚的可以參考: Huffman Coding

解壓:Huffman Coding 之後需要維護一張Huffman Map 表,來記錄重新編碼後的字符串,根據這張表,還原原始串也是非常高效的。

Deflate 綜合使用了LZ77 和Huffman Coding 來壓縮文件,相對而言又提升了很多。詳細可以參考gzip原理與實現

網站中的使用

RFC 2016中GZIP已經成為了規定的三種標準HTTP壓縮格式之一。目前絕大多數的網站都在使用GZIP 傳輸HTML、CSS、JavaScript 等資源文件。

Nginx開啟

Nginx的ngx_http_gzip_module也提供了開啟GZIP壓縮的方式,有下面的一些常用配置:

# 开启gzip on ; # 压缩等级,1-9。设置多少可以参考:http://serverfault.com/questions/253074/what-is-the-best-nginx-compression-gzip-level gzip_comp_level 2 ; # "MSIE [1-6]." 比如禁止IE6 使用GZIP gzip_disable regex ... # 最小压缩文件长度gzip_min_length 20 ; # 使用GZIP 压缩的最小HTTP 版本gzip_http_version 1.1 ; # 压缩的文件类型,值是[MIME type](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types) gzip_types text/html ;

相關探測

Nginx 上開啟GZIP 之後,理論上會按照GZIP 配置打開壓縮。那如何檢測是否開啟成功了呢?

打開瀏覽器,訪問你的網站,看Chrome 的Network,點use larger request row,如果Size 上有兩個不一樣大小的體積(如:222KB 和613KB),則代表GZIP 已經成功開啟。

那瀏覽器又是如何和服務器配合的呢?

瀏覽器在請求資源的時候再header裡面帶上accept-encoding: gzip的參數。 Nginx 在接收到Header 之後,發現如果有這個配置,則發送GZIP 之後的文件(返回的header 裡也包含相關的說明),如果沒有則發送源文件。瀏覽器根據response header 來處理要不要針對返回的文件進行解壓縮然後展示。

參考文檔

原文地址:

What do you think?

Written by marketer

Webpack官方成員Sean確認出席FEDAY,發表演講

React 官方團隊最近在幹嘛?