簡單聊聊 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 <gzip@prep.ai.mit.edu> 發郵件申請。目前 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 的滑動窗口。如果發現掃描頭處的串和窗口里的 最長匹配串 是相同的,則用(兩個串之間的距離,串的長度)來代替后一個重復的串,同時還需要添加一個表示是真實串還是替換后的“串”的字節在前面以方便解壓(此串需要在 真實串和替換“串” 之前都有存在)。

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

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

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

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

Huffman Coding

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

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

解壓: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 配置打開壓縮。那如何檢測是否開啟成功了呢?

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

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

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

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

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

參考文檔

原文鏈接:github.com/rccoder/blo…

登錄后免費查看全文
立即登錄
App下載
技術鄰APP
工程師必備
  • 項目客服
  • 培訓客服
  • 平臺客服

TOP