【Node.js】青梅煮酒,聊聊zlib壓縮

語言: CN / TW / HK

theme: awesome-green highlight: atelier-dune-light


「本文正在參與技術專題徵文Node.js進階之路,點擊查看詳情

前言

完成對Node.js的從瞭解到熟練的進階這個Flag設立已久,久到去年就有它了。驚蟄已過,風暖雲開,隔年的Flag是時候拿出來實現了。出去踏青or在家碼字,我決定選擇後者。

至少對Node.js的探索,今年能有一個完美的歎號。

目標明確

這個明媚的春日,特別適合窗前看書。光影斑駁,再來杯白開水。最近看zlib壓縮的API,發現無論從理解還是使用上都比較陌生,所以挑了一些看着感興趣的API進行進一步的摸索。

隨波逐流無歸處,乘風破浪濟滄海

zlib 壓縮

瞧一瞧,一個壓縮/解壓功能包含了多少知識點?

文件壓縮和解壓的實現

```js let zlib = require('zlib'); const { createReadStream, createWriteStream } = require('fs'); const { pipeline } = require('stream');

/* * 壓縮過程中錯誤捕獲方法 / const onError = err => { if (err) { console.error('An error occurred:', err); process.exitCode = 1; } };

/* * 壓縮或者解壓方法 type值為zip執行壓縮方法,type值為ungzip執行解壓縮方法 / function zipFunc(source, destination, type) { const gzip = zlib.createGzip(); const ungzip = zlib.createGunzip(); switch (type) { case 'zip': return pipeline(source, gzip, destination, onError); case 'ungzip': // 或者用pipeline方法,return pipeline(source, ungzip, destination, onError); return source.pipe(ungzip).pipe(destination); default: return pipeline(source, gzip, destination, onError); } } // 壓縮 const source = createReadStream('./zlib/input.txt'); const destination = createWriteStream('./zlib/input.txt.gz'); zipFunc(source, destination, 'zip');

// 解壓 const source = createReadStream('./zlib/input.txt.gz'); const destination = createWriteStream('./zlib/input.txt'); zipFunc(source, destination, 'ungzip'); ```

  • 執行壓縮操作時,zlib目錄下生成input.txt.gz文件;
  • 執行解壓操作時,zlib目錄下生成input.txt文件;

zlib-01.png

pipeline

stream.pipeline()方法,用於在流和生成器之間進行管道轉發錯誤並正確清理並在管道完成時提供回調。

難以理解的功能介紹?

上面這段功能介紹,我看了好多遍, 並沒有理解,尤其是對於管道的概念比較模糊。於是搜了一下stream的文章,發現了一篇好文《Node.js 中的一股清流:理解 Stream(流)的基本概念》,寫的很詳細易懂,它裏有這樣一段話:

管道是一種機制,是將一個流的輸出作為另一流的輸入。它通常用於從一個流中獲取數據並將該流的輸出傳遞到另外的流。管道操作沒有限制,換句話説,管道用於分步驟處理流數據。

所以在進行文件壓縮的時候使用stream.pipeline()提供一個完成數據流處理的管道,管道內可以傳輸多個流,管道任務結束後提供回調。

用法

js stream.pipeline(source[, ...transforms], destination, callback)

屬性

source:可讀流

...tranforms:雙工流(同時實現 Readable 和 Writable 接口的流)

destination:可寫流

callback:管道完成時的回調

pipe

readable.pipe() 方法將可寫流綁定到可讀流,使其自動切換到流動模式並將其所有數據推送到綁定的可寫流。 將這句話總結一下,pipe方法的主要用途是從可讀流中讀取數據寫入可寫流。

用法

js readable.pipe(destination[, options])

示例

可以看官方的示例,簡單易懂,將 readable 中的所有數據通過管道傳輸到名為 file.txt 的文件中:

js const fs = require('fs'); const readable = getReadableStreamSomehow(); const writable = fs.createWriteStream('file.txt'); // 可讀流的所有數據進入 'file.txt'。 readable.pipe(writable);

也可以將多個 Writable 流綁定到單個 Readable 流。

readable.pipe() 方法返回對目標流的引用,從而可以建立管道流鏈

js 也可以將多個 Writable 流綁定到單個 Readable 流。 readable.pipe() 方法返回對目標流的引用,從而可以建立管道流鏈

js const fs = require('fs'); const r = fs.createReadStream('file.txt'); const z = zlib.createGzip(); const w = fs.createWriteStream('file.txt.gz'); r.pipe(z).pipe(w);

stream 流

什麼是流?

看下官網的介紹。

流是用於在 Node.js 中處理流數據的抽象接口。 stream 模塊提供了用於實現流接口的 API。

流可以是可讀的、可寫的、或兩者兼而有之。 所有的流都是 EventEmitter 的實例。

我看完,好像懂了又好像沒懂。但是我找到了一篇講的非常好的文章,《一文搞定 Node.js 流 (Stream)》

這篇文章裏面對流的介紹,我感覺懂了一些

stream(流)是一種抽象的數據結構。就像數組或字符串一樣,流是數據的集合。

不同的是,流可以每次輸出少量數據,而且它不用存在內存中。

比如,對服務器發起 http 請求的 request/response 對象就是 Stream。

總結一下,使用流可以將文件資源拆分成小塊進行處理,減輕服務器壓力。

明白了流的作用,就知道為什麼文件壓縮要使用Stream提供的模塊方法了。如果想對Stream進行更深入的瞭解,推薦閲讀《一文搞定 Node.js 流 (Stream)》,寫的詳情且通俗易懂。

壓縮 HTTP 的請求和響應

gzip、deflate 和 br

  • gzip是一種數據格式,默認且目前僅使用deflate算法壓縮data部分;
  • deflate是同時使用了LZ77算法與哈夫曼編碼(Huffman Coding)的一個無損數據壓縮算法。
  • Brotli 通過變種的 LZ77 算法、Huffman 編碼以及二階文本建模等方式進行數據壓縮,與其他壓縮算法相比,它有着更高的壓縮效率。

官網示例的本地實驗

我再官網給出的示例的基礎上,將http的響應內容生成不同的文件,可以看出壓縮過和未經過壓縮的文件的文件大小是有區別的。

示例代碼

```js // 客户端請求示例 const zlib = require('zlib'); const http = require('http'); const fs = require('fs'); const { pipeline } = require('stream');

const request = http.get({ host: 'example.com', path: '/', port: 80, headers: { 'Accept-Encoding': 'br,gzip,deflate' }, }); request.on('response', response => { console.log(response.headers['content-encoding'], 'headers'); const output = fs.createWriteStream('./zlib/example.com_index_default.html'); // const output = fs.createWriteStream('./zlib/example.com_index_gzip.html'); // const output = fs.createWriteStream('./zlib/example.com_index_deflate.html');

const onError = err => { if (err) { console.error('An error occurred:', err); process.exitCode = 1; } };

switch (response.headers['content-encoding']) { case 'br': pipeline(response, zlib.createBrotliDecompress(), output, onError); break; case 'gzip': pipeline(response, zlib.createGzip(), output, onError); break; case 'deflate': pipeline(response, zlib.createGzip(), output, onError); break; default: pipeline(response, output, onError); break; } }); ``` - 未經過壓縮的文件大小是1.2k; - 壓縮過的文件大小是600多B;

zlib-02.png

小結

對http請求和響應的壓縮,我還有待在實際應用場景中研究和實踐,單純實現官網的例子,我感覺自己沒有完全掌握。

總結

在過去一個月的學習中,雖然都是碎片化的學習,但是隨着技術的積累,形成一套屬於自己的學習體系,可以幫助更快更好的掌握新技術。

接下來,就是實踐的階段了,雖然工作中沒有使用Node.js開發的場景,但是自己可以創造項目,正好我有一個現成的小程序,可以開發一套文章管理後台系統。