相關推薦:《》
雙工流就是同時實現了 Readable 和 Writable 的流,即可以作為上游生產資料,又可以作為下游消費資料,這樣可以處於資料流動管道的中間部分,即
rs.pipe(rws1).pipe(rws2).pipe(rws3).pipe(ws);
在 NodeJS 中雙工流常用的有兩種
Duplex
Transform
實現 Duplex
和 Readable、Writable 實現方法類似,實現 Duplex 流非常簡單,但 Duplex 同時實現了 Readable 和 Writable, NodeJS 不支援多繼承,所以我們需要繼承 Duplex 類
繼承 Duplex 類
實現 _read() 方法
實現 _write() 方法
相信大家對 read()、write() 方法的實現不會陌生,因為和 Readable、Writable 完全一樣。
const Duplex = require('stream').Duplex; const myDuplex = new Duplex({ read(size) { // ... }, write(chunk, encoding, callback) { // ... } });
建構函式引數
Duplex 範例內同時包含可讀流和可寫流,在範例化 Duplex 類的時候可以傳遞幾個引數
小例子
瞭解了 Readable 和 Writable 之後看 Duplex 非常簡單,直接用一個官網的例子
const Duplex = require('stream').Duplex; const kSource = Symbol('source'); class MyDuplex extends Duplex { constructor(source, options) { super(options); this[kSource] = source; } _write(chunk, encoding, callback) { // The underlying source only deals with strings if (Buffer.isBuffer(chunk)) chunk = chunk.toString(); this[kSource].writeSomeData(chunk); callback(); } _read(size) { this[kSource].fetchSomeData(size, (data, encoding) => { this.push(Buffer.from(data, encoding)); }); } }
當然這是不能執行的虛擬碼,但是 Duplex 的作用可見一斑,進可以生產資料,又可以消費資料,所以才可以處於資料流動管道的中間環節,常見的 Duplex 流有
Transform 同樣是雙工流,看起來和 Duplex 重複了,但兩者有一個重要的區別:Duplex 雖然同事具備可讀流和可寫流,但兩者是相對獨立的;Transform 的可讀流的資料會經過一定的處理過程自動進入可寫流。
雖然會從可讀流進入可寫流,但並不意味這兩者的資料量相同,上面說的一定的處理邏輯會決定如果 tranform 可讀流,然後放入可寫流,transform 原義即為轉變,很貼切的描述了 Transform 流作用。
我們最常見的壓縮、解壓縮用的 zlib 即為 Transform 流,壓縮、解壓前後的資料量明顯不同,兒流的作用就是輸入一個 zip 包,輸入一個解壓檔案或反過來。我們平時用的大部分雙工流都是 Transform。
實現 Tranform
Tranform 類內部繼承了 Duplex 並實現了 writable.write() 和 readable._read() 方法,我們想自定義一個 Transform 流,只需要
繼承 Transform 類
實現 _transform() 方法
實現 _flush() 方法(可以不實現)
_transform(chunk, encoding, callback) 方法用來接收資料,併產生輸出,引數我們已經很熟悉了,和 Writable 一樣, chunk 預設是 Buffer,除非 decodeStrings 被設定為 false。
在 _transform() 方法內部可以呼叫 this.push(data) 生產資料,交給可寫流,也可以不呼叫,意味著輸入不會產生輸出。
當資料處理完了必須呼叫 callback(err, data) ,第一個引數用於傳遞錯誤資訊,第二個引數可以省略,如果被傳入了,效果和 this.push(data) 一樣
transform.prototype._transform = function (data, encoding, callback) { this.push(data); callback(); }; transform.prototype._transform = function (data, encoding, callback) { callback(null, data); };
有些時候,transform 操作可能需要在流的最後多寫入可寫流一些資料。例如, Zlib流會儲存一些內部狀態,以便優化壓縮輸出。在這種情況下,可以使用_flush()方法,它會在所有寫入資料被消費、觸發 'end'之前被呼叫。
Transform 流有兩個常用的事件
來自 Writable 的 finish
來自 Readable 的 end
當呼叫 transform.end() 並且資料被 _transform() 處理完後會觸發 finish,呼叫_flush後,所有的資料輸出完畢,觸發end事件。
瞭解了 Readable 和 Writable 之後,理解雙工流十分自然,但兩者的區別會讓一些初學者困惑,簡單的區分:Duplex 的可讀流和可寫流之間並沒有直接關係,Transform 中可讀流的資料會經過處理後自動放入可寫流中。
看兩個簡單的例子就能直觀瞭解到 Duplex 和 Transform 的區別
TCP socket
net 模組可以用來建立 socket,socket 在 NodeJS 中是一個典型的 Duplex,看一個 TCP 使用者端的例子
var net = require('net'); //建立使用者端 var client = net.connect({port: 1234}, function() { console.log('已連線到伺服器'); client.write('Hi!'); }); //data事件監聽。收到資料後,斷開連線 client.on('data', function(data) { console.log(data.toString()); client.end(); }); //end事件監聽,斷開連線時會被觸發 client.on('end', function() { console.log('已與伺服器斷開連線'); });
可以看到 client 就是一個 Duplex,可寫流用於向伺服器傳送訊息,可讀流用於接受伺服器訊息,兩個流內的資料並沒有直接的關係。
gulp
gulp 非常擅長處理程式碼本地構建流程,看一段官網的範例程式碼
gulp.src('client/templates/*.jade') .pipe(jade()) .pipe(minify()) .pipe(gulp.dest('build/minified_templates'));
其中 jada() 和 minify() 就是典型的 Transform,處理流程大概是
.jade 模板檔案 -> jada() -> html 檔案 -> minify -> 壓縮後的 html
可以看出來,jade() 和 minify() 都是對輸入資料做了些特殊處理,然後交給了輸出資料。
這樣簡單的對比就能看出 Duplex 和 Transform 的區別,在平時實用的時候,當一個流同事面向生產者和消費者服務的時候我們會選擇 Duplex,當只是對資料做一些轉換工作的時候我們便會選擇使用 Tranform。
更多程式設計相關知識,請存取:!!
以上就是淺談Nodejs中的雙工流的詳細內容,更多請關注TW511.COM其它相關文章!