Node.js實戰--資源壓縮與zlib模塊


📖Blog:《NodeJS模塊研究 - zlib》

🐱Github:https://github.com/dongyuanxin/blog

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 文件),都可以采用哈夫曼樹進行壓縮。

參考鏈接

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM