
📖Blog:《NodeJS模塊研究 - zlib》
nodejs 的 zlib 模塊提供了資源壓縮功能。例如在 http 傳輸過程中常用的 gzip,能大幅度減少網絡傳輸流量,提高速度。本文將從下面幾個方面介紹 zlib 模塊和相關知識點:
-
文件壓縮 / 解壓 -
HTTP 中的壓縮/解壓 -
壓縮算法:RLE -
壓縮算法:哈夫曼樹
文件的壓縮/解壓
以 gzip 壓縮為例,壓縮代碼如下:
const zlib = require("zlib");
const fs = require("fs");
const gzip = zlib.createGzip();
const rs = fs.createReadStream("./db.json");
const ws = fs.createWriteStream("./db.json.gz");
rs.pipe(gzip).pipe(ws);
如下圖所示,4.7Mb 大小的文件被壓縮到了 575Kb。

解壓剛才壓縮后的文件,代碼如下:
const zlib = require("zlib");
const fs = require("fs");
const gunzip = zlib.createGunzip();
const rs = fs.createReadStream("./db.json.gz");
const ws = fs.createWriteStream("./db.json");
rs.pipe(gunzip).pipe(ws);
HTTP 中的壓縮/解壓
在服務器中和客戶端的傳輸過程中,瀏覽器(客戶端)通過 Accept-Encoding 消息頭來告訴服務端接受的壓縮編碼,服務器通過 Content-Encoding 消息頭來告訴瀏覽器(客戶端)實際用於編碼的算法。
服務器代碼示例如下:
const zlib = require("zlib");
const fs = require("fs");
const http = require("http");
const server = http.createServer((req, res) => {
const rs = fs.createReadStream("./index.html");
// 防止緩存錯亂
res.setHeader("Vary", "Accept-Encoding");
// 獲取客戶端支持的編碼
let acceptEncoding = req.headers["accept-encoding"];
if (!acceptEncoding) {
acceptEncoding = "";
}
// 匹配支持的壓縮格式
if (/\bdeflate\b/.test(acceptEncoding)) {
res.writeHead(200, { "Content-Encoding": "deflate" });
rs.pipe(zlib.createDeflate()).pipe(res);
} else if (/\bgzip\b/.test(acceptEncoding)) {
res.writeHead(200, { "Content-Encoding": "gzip" });
rs.pipe(zlib.createGzip()).pipe(res);
} else if (/\bbr\b/.test(acceptEncoding)) {
res.writeHead(200, { "Content-Encoding": "br" });
rs.pipe(zlib.createBrotliCompress()).pipe(res);
} else {
res.writeHead(200, {});
rs.pipe(res);
}
});
server.listen(4000);
客戶端代碼就很簡單了,識別 Accept-Encoding 字段,並進行解壓:
const zlib = require("zlib");
const http = require("http");
const fs = require("fs");
const request = http.get({
host: "localhost",
path: "/index.html",
port: 4000,
headers: { "Accept-Encoding": "br,gzip,deflate" }
});
request.on("response", response => {
const output = fs.createWriteStream("example.com_index.html");
<span class="hljs-keyword" style="color: #333; font-weight: bold; line-height: 26px;">switch</span> (response.headers[<span class="hljs-string" style="color: #d14; line-height: 26px;">"content-encoding"</span>]) {
<span class="hljs-keyword" style="color: #333; font-weight: bold; line-height: 26px;">case</span> <span class="hljs-string" style="color: #d14; line-height: 26px;">"br"</span>:
response.pipe(zlib.createBrotliDecompress()).pipe(output);
<span class="hljs-keyword" style="color: #333; font-weight: bold; line-height: 26px;">break</span>;
<span class="hljs-comment" style="color: #998; font-style: italic; line-height: 26px;">// 或者, 只是使用 zlib.createUnzip() 方法去處理這兩種情況:</span>
<span class="hljs-keyword" style="color: #333; font-weight: bold; line-height: 26px;">case</span> <span class="hljs-string" style="color: #d14; line-height: 26px;">"gzip"</span>:
response.pipe(zlib.createGunzip()).pipe(output);
<span class="hljs-keyword" style="color: #333; font-weight: bold; line-height: 26px;">break</span>;
<span class="hljs-keyword" style="color: #333; font-weight: bold; line-height: 26px;">case</span> <span class="hljs-string" style="color: #d14; line-height: 26px;">"deflate"</span>:
response.pipe(zlib.createInflate()).pipe(output);
<span class="hljs-keyword" style="color: #333; font-weight: bold; line-height: 26px;">break</span>;
<span class="hljs-keyword" style="color: #333; font-weight: bold; line-height: 26px;">default</span>:
response.pipe(output);
<span class="hljs-keyword" style="color: #333; font-weight: bold; line-height: 26px;">break</span>;
}
});
從上面的例子可以看出來,3 種對應的解壓/壓縮 API:
-
zlib.createInflate()
和zlib.createDeflate()
-
zlib.createGunzip()
和zlib.createGzip()
-
zlib.createBrotliDecompress()
和zlib.createBrotliCompress()
壓縮算法:RLE
RLE 全稱是 Run Length Encoding, 行程長度編碼,也稱為游程編碼。它的原理是:記錄連續重復數據的出現次數。它的公式是:字符 * 出現次數
。
例如原數據是 AAAAACCPPPPPPPPERRPPP
,一共 18 個字節。按照 RLE 的規則,壓縮后的結果是:A5C2P8E1R2P3
,一共 12 個字節。壓縮比例是:12 / 17 = 70.6%
RLE 的優點是壓縮和解壓非常快,針對連續出現的多個字符的數據壓縮率更高。但對於ABCDE
類似的數據,壓縮后數據會更大。
壓縮算法:哈夫曼樹
哈夫曼樹的原理是:出現頻率越高的字符,用盡量更少的編碼來表示。按照這個原理,以數據ABBCCCDDDD
為例:
字符 | 編碼(二進制) |
---|---|
D | 0 |
C | 1 |
B | 10 |
A | 11 |
原來的數據是 10 個字節。那么編碼后的數據是:1110101110000
,一共 13bit,在計算機中需要 2 個字節來存儲。這樣的壓縮率是:2 / 10 = 20%。
但是僅僅按照這個原理編碼后的數據,無法正確還原。以前 4bit 為例,1110
可以理解成:
-
11 + 10 -
1 + 1 + 1 + 0 -
1 + 1 + 10 -
...
而哈夫曼樹的設計就很巧妙,能正確還原。哈夫曼樹的構造過程如下:

無論哪種數據類型(文本文件、圖像文件、EXE 文件),都可以采用哈夫曼樹進行壓縮。
參考鏈接

👇掃碼關注「心譚博客」,查看「前端圖譜」&「算法題解」,堅持分享,共同成長👇
