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

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

🐱Github:github.com/dongyuanxin…html

nodejs 的 zlib 模塊提供了資源壓縮功能。例如在 http 傳輸過程當中經常使用的 gzip,能大幅度減小網絡傳輸流量,提升速度。本文將從下面幾個方面介紹 zlib 模塊和相關知識點:前端

  • 文件壓縮 / 解壓
  • HTTP 中的壓縮/解壓
  • 壓縮算法:RLE
  • 壓縮算法:哈夫曼樹

文件的壓縮/解壓

以 gzip 壓縮爲例,壓縮代碼以下:java

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。node

解壓剛纔壓縮後的文件,代碼以下:git

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 消息頭來告訴瀏覽器(客戶端)實際用於編碼的算法。程序員

服務器代碼示例以下:github

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");

    switch (response.headers["content-encoding"]) {
        case "br":
            response.pipe(zlib.createBrotliDecompress()).pipe(output);
            break;
        // 或者, 只是使用 zlib.createUnzip() 方法去處理這兩種狀況:
        case "gzip":
            response.pipe(zlib.createGunzip()).pipe(output);
            break;
        case "deflate":
            response.pipe(zlib.createInflate()).pipe(output);
            break;
        default:
            response.pipe(output);
            break;
    }
});
複製代碼

從上面的例子能夠看出來,3 種對應的解壓/壓縮 API:json

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

參考連接

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

相關文章
相關標籤/搜索