前端性能優化gzip初探(補充gzip壓縮使用算法brotli壓縮的相關介紹)

一般在看一些面試題問到前端有哪些性能優化手段的時候,可能會提到一個叫作gzip壓縮的方法。正好最近在學習node文件流操做和zlib模塊的時候,對gzip壓縮有了一個新的認識。今天就和你們一塊兒分享一下,gzip是什麼,從瀏覽器請求到收到服務端數據發生了什麼。javascript

因爲以前的題目《你知道前端性能優化gzip的工做原理嗎?》有些歧義,我原本想說的工做原理是這個過程當中發生了什麼,而不少點進來的大佬想看到的是壓縮算法實現。因此將標題改成《前端性能優化gzip初探》,可能後續會對gzip壓縮實現的一些粗淺的認識補上。小弟不甚惶恐,請你們見諒。 css

什麼是gzip

兄弟你據說winRAR嗎?據說過360壓縮,快壓,好壓嗎?都據說過,那你聽過GNUzip嗎?html

對,沒有錯,gzip就是GNUzip的縮寫,也是一個文件壓縮程序,能夠將文件壓縮進後綴爲.gz的壓縮包。而咱們前端所講的gzip壓縮優化,就是經過gzip這個壓縮程序,對資源進行壓縮,從而下降請求資源的文件大小。前端

gzip壓縮優化在業界的應用有多麼廣泛呢,基本上你打開任何一個網站,看它們的html,js,css文件都是通過gzip壓縮的(即便js,css這類文件通過了混淆壓縮以後,gzip仍然能夠明顯的優化文件體積。)。java

Tips:一般gzip對純文本內容可壓縮到原大小的40%。但png、gif、jpg、jpeg這類圖片文件並不推薦使用gzip壓縮(svg是個例外),首先通過壓縮後的圖片文件gzip能壓縮的空間很小。事實上,添加標頭,壓縮字典,並校驗響應體可能會讓它更大。 node

好比如今,你正在訪問的掘金,打開調試工具,在網絡請求Network中,選擇一個js或css,都能在Response Headers中找到 content-encoding: gzip 鍵值對,這就表示了這個文件是啓用了gzip壓縮的。webpack

gzip壓縮過程

上面咱們能夠看到,這裏是掘金網站引入的一個growingIO數據分析的文件,通過了gzip壓縮,大小是25.3K。如今咱們把這個文件下載下來,建一個沒有開啓gzip的本地服務器,看看未開啓gzip壓縮這個文件是多大(其實下載下來就已經能看到文件大小了,是88.73k)。nginx

此處咱們用原生node寫一個服務,便於咱們學習理解,目錄和代碼以下:web

const http = require("http");
const fs = require("fs");

const server = http.createServer((req, res) => {
  const rs = fs.createReadStream(`static${req.url}`); //讀取文件流
  rs.pipe(res); //將數據以流的形式返回
  rs.on("error", err => {
    //找不到返回404
    console.log(err);
    res.writeHead(404);
    res.write("Not Found");
  });
});
//監聽8080
server.listen(8080, () => {
  console.log("listen prot:8080");
});

複製代碼

node server.js啓動服務,此時咱們訪問http://localhost:8080/vds.js,網頁會顯示vds.js文件的內容,查看Network面版,會發現vds.js請求大小是88.73k,和原始資源文件大小一致,Response Headers中也沒有 content-encoding: gzip ,說明這是未通過gzip壓縮的。面試

如何開啓gzip呢,很簡單,node爲咱們提供了zlib模塊,直接使用就行,上面的代碼簡單修改一下就能夠。

const http = require("http");
const fs = require("fs");
const zlib = require("zlib"); // <-- 引入zlib塊

const server = http.createServer((req, res) => {
  const rs = fs.createReadStream(`static${req.url}`);
  const gz = zlib.createGzip(); // <-- 建立gzip壓縮
  rs.pipe(gz).pipe(res); // <-- 返回數據前通過gzip壓縮
  rs.on("error", err => {
    console.log(err);
    res.writeHead(404);
    res.write("Not Found");
  });
});

server.listen(8080, () => {
  console.log("listen prot:8080");
});

複製代碼

運行這段代碼,訪問http://localhost:8080/vds.js,會發現網頁沒有顯示vds.js內容,而是直接下載了一個vds.js文件,大小是25k,大小好像是通過了壓縮的。可是若是你嘗試用編輯器打開這個文件,會發現打開失敗或者提示這是一個二進制文件而不是文本。這個時候若是反應快的朋友可能會和我第一次的想法同樣,試試把js後綴改爲gz。由於前面說了,其實gzip就是一個壓縮程序,將文件壓縮進一個.gz壓縮包。這個地方會不會實際上是一個gz壓縮包?

不賣關子了,將後綴名改成gz,解壓成功後會出來一個88.73k的vds.js。

相信到了這裏你們都應該豁然開朗,原來gzip就是將資源文件壓縮進一個壓縮包裏啊,可是惟一的問題是這壓縮包我怎麼用,我請求一個文件,服務器你卻給我一個壓縮包,我識別不了啊。

解決這個問題更簡單,服務端返回壓縮包的時候告訴瀏覽器一聲,這實際上是一個gz壓縮包,瀏覽器你使用前先解壓一下。而這個通知就是咱們以前判斷是否開啓gzip壓縮的請求頭字段,Response Headers裏的 content-encoding: gzip

咱們最後修改一下代碼,加一個請求頭:

const http = require("http");
const fs = require("fs");
const zlib = require("zlib"); 

const server = http.createServer((req, res) => {
  const rs = fs.createReadStream(`static${req.url}`);
  const gz = zlib.createGzip(); 
  res.setHeader("content-encoding", "gzip"); //添加content-encoding: gzip請求頭。
  rs.pipe(gz).pipe(res); 
  rs.on("error", err => {
    console.log(err);
    res.writeHead(404);
    res.write("Not Found");
  });
});

server.listen(8080, () => {
  console.log("listen prot:8080");
複製代碼

此時瀏覽器再請求到gzip壓縮後的文件,會先解壓處理一下再使用,這對於咱們用戶來講是無感知的,工做瀏覽器都在背後默默作了,咱們只是看到網絡請求文件的大小,比服務器上實際資源的大小小了不少。

這一段花了很長的篇幅來說gzip的工做原理,明白以後其實真的很簡單,並且之後問到前端性能優化這一點,相信gzip這條應該是不會忘了的。

gzip的注意點

前面說的哪些文件適合開啓gzip壓縮,哪些不適合是一個注意點。

還有一個注意點是,誰來作這個gzip壓縮,咱們的例子是在接到請求時,由node服務器進行壓縮處理。這和express中使用compression中間件,koa中使用koa-compress中間件,nginx和tomcat進行配置都是同樣的,這也是比較廣泛的一種作法,由服務端進行壓縮處理。

服務器瞭解到咱們這邊有一個 gzip 壓縮的需求,它會啓動本身的 CPU 去爲咱們完成這個任務。而壓縮文件這個過程自己是須要耗費時間的,你們能夠理解爲咱們以服務器壓縮的時間開銷和 CPU 開銷(以及瀏覽器解析壓縮文件的開銷)爲代價,省下了一些傳輸過程當中的時間開銷。

若是咱們在構建的時候,直接將資源文件打包成gz壓縮包,其實也是能夠的,這樣能夠省去服務器壓縮的時間,減小一些服務端的消耗。

好比咱們在使用webpack打包工具的時候可使用compression-webpack-plugin插件,在構建項目的時候進行gzip打包,詳細的配置使用能夠去看插件的文檔,很是簡單。

補充內容:gzip文件分析

開頭曾經提到過gzip是一個壓縮程序而並非一個算法,通過gzip壓縮後文件格式爲.gz,咱們對.gz文件進行分析。

使用node的fs模塊去讀取一個gz壓縮包能夠看到以下一段Buffer內容:

const fs = require("fs");

fs.readFile("vds.gz", (err, data) => {
  console.log(data); // <Buffer 1f 8b 08 00 00 00 00 00 00 0a ... >
});

複製代碼

一般gz壓縮包有文件頭,文件體和文件尾三個部分。頭尾專門用來存儲一些文件相關信息,好比咱們看到上面的Buffer數據,第一二個字節爲1f 8b(16進制),一般第一二字節爲1f 8b就能夠初步判斷這是一個gz壓縮包,可是具體仍是要看是否徹底符合gz文件格式,第三個字節取值範圍是0到8,目前只用8,表示使用的是Deflate壓縮算法。還有一些好比修改時間,壓縮執行的文件系統等信息也會在文件頭。

而文件尾會標識出一些原始數據大小的相關信息,被壓縮的數據則是放在中間的文件體。

前面所說的,對於已經壓縮過的圖片,開啓了gzip壓縮反而可能會使其變得更大,就是由於中間實際壓縮體沒怎麼減少,可是卻添加了頭尾的壓縮相關信息。

補充內容:gzip的壓縮算法

gzip中間的文件體,使用的是Deflate算法,這是一種無損壓縮解壓算法。Deflate是zip壓縮文件的默認算法,7z,xz等其餘的壓縮文件中都有用到,實際上deflate只是一種壓縮數據流的算法. 任何須要流式壓縮的地方均可以用。

Deflate算法進行壓縮時,通常先用Lz77算法壓縮,再使用Huffman編碼。

Lz77算法的原理是,若是文件中有兩塊內容相同的話,咱們能夠用二者之間的距離,相同內容的長度這樣一對信息,來替換後一塊內容。因爲二者之間的距離,相同內容的長度這一對信息的大小,小於被替換內容的大小,因此文件獲得了壓縮。

舉個例子:

http://www.baidu.com https://www.taobao.com

上面一段文本能夠看到,先後有部份內容是相同的,咱們能夠用前文相同內容的距離和相同字符長度替換後文的內容。

http://www.baidu.com (21,12)taobao(23,4)

Deflate採用的Lz77算法是通過改進的版本,首先三個字節以上的重複串才進行偏碼,不然不進行編碼。其次匹配查找的時候用了哈希表,一個head數組記錄最近匹配的位置和prev鏈表來記錄哈希值衝突的以前的匹配位置。

而Huffman編碼,由於理解的不是很清楚,這裏就不便多說了,只大概瞭解是經過字符出現機率,將高頻字符用較短字節進行表示從而達到字符串的壓縮。

其實簡單的看一下這些算法,咱們大概能明白,爲何js,css這些文件即便通過了工具的混淆壓縮,經過gzip依然能獲得可觀的壓縮優化。

更多的gzip算法內容能夠閱讀下面的文章:

GZIP壓縮原理分析系列

補充內容:brotli壓縮

感謝wangyjx1的評論,特別去了解了一下brotli壓縮,這裏將瞭解到的一些內容分享出來。

Brotli由google在2015年推出,用於網絡字體的離線壓縮,後發佈包含通用無損數據壓縮的Brotli加強版本,Brotli基於LZ77算法的一個現代變體、Huffman編碼和二階上下文建模。

與常見的通用壓縮算法不一樣,Brotli使用一個預約義的120千字節字典。該字典包含超過13000個經常使用單詞、短語和其餘子字符串,這些來自一個文本和HTML文檔的大型語料庫。預約義的算法能夠提高較小文件的壓縮密度。使用brotli取代deflate來對文本文件壓縮一般能夠增長20%的壓縮密度,而壓縮與解壓縮速度則大體不變。

目前該壓縮方式大部分瀏覽器(包括移動端)新版本支持良好,詳細的支持狀況可在caniuse查詢到。

支持Brotli壓縮算法的瀏覽器使用的內容編碼類型爲br,例如如下是Chrome瀏覽器請求頭裏Accept-Encoding的值:

Accept-Encoding: gzip, deflate, sdch, br

若是服務端支持Brotli算法,則會返回如下的響應頭:

Content-Encoding: br

Tips:brotli 壓縮只能在 https 中生效,由於 在 http 請求中 request header 裏的 Accept-Encoding: gzip, deflate 是沒有 br 的。

目前該壓縮方案的使用狀況,去查看了幾大網站的網絡請求,國外的google,facebook,bing都已用上了Brotli壓縮。國內的話淘寶,百度,騰訊,京東,b站幾個大站基本都沒有使用,惟一我發現使用了brotli壓縮大家猜是哪一個網站?是知乎,果真有逼格,掘金能夠考慮跟上了。好在騰訊雲,阿里雲,又拍雲這類的cdn加速服務商都支持了brotli壓縮。

node中沒有原生模塊支持brotli壓縮,可使用第三方庫來支持,好比iltorb,感興趣的朋友能夠本身嘗試一下(反正新瞭解的東西,我確定是要親自動手搞一下才行)。

最後

十分抱歉以前的標題和內容讓你們產生誤解,掘金的大佬們真的很嚴格。這文的寫做原因只是由於學習node中理解了gzip的工做過程,想分享給你們。沒敢講壓縮算法這塊,是由於這地方自己還要學習,並且對於前端來講不是必須掌握的知識點,面試應該不會問這麼深,固然感興趣的能夠本身瞭解。可是既然你們但願能提到這些,我就獻醜簡單分享一下我知道的東西,有不足之處歡迎你們批評指正。

文章列表

相關文章
相關標籤/搜索