「譯」Node.js Streams 基礎

Node.js 天生異步和事件驅動,很是適合處理 I/O 相關的任務。若是你在處理應用中 I/O 相關的操做,你能夠利用 Node.js 中的流(stream)。所以,咱們先具體看看流,理解一下它們是怎麼簡化 I/O 操做的吧。node

流是什麼

流是 unix 管道,讓你能夠很容易地從數據源讀取數據,而後流向另外一個目的地。
簡單來講,流不是什麼特別的東西,它只是一個實現了一些方法的 EventEmitter。根據它實現的方法,流能夠變成可讀流(Readable),可寫流(Writable),或者雙向流(Duplex,同時可讀可寫)。
可讀流能讓你從一個數據源讀取數據,而可寫流則可讓你往目的地寫入數據。git

若是你已經用過 Node.js,你極可能已經遇到過流了。
例如,在一個 Node.js 的 HTTP 服務器裏面,request 是一個可讀流,response 是一個可寫流。
你也可能用過 fs 模塊,它能幫你處理可讀可寫流。github

如今讓你學一些基礎,理解不一樣類型的流。本文會討論可讀流和可寫流,雙向流超出了本文的討論範圍,咱們不做討論。瀏覽器

可讀流 (Readable Streams)

咱們能夠用可讀流從一個數據源中讀取數據,這個數據源能夠是任何東西,例如系統中的一個文件,內存中的 buffer,甚至是其餘流。由於流是 EventEmitter,它們會用各類事件發送數據。咱們會利用這些事件來讓流工做。服務器

從流中讀取數據

從流中讀取數據最好的方式是監聽 data 事件,添加一個回調函數。當有數據流過來的時候,可讀流會發送 data 事件,回調函數就會觸發。看看下面的代碼片斷:異步

var fs = require('fs');
var readableStream = fs.createReadStream('file.txt');
var data = '';

var readableStream.on('data', function(chunk) {
  data += chunk;
});

readableStream.on('end', function() {
  console.log(data);
});

fs.createReadStream 會給你一個可讀流。
最開始的時候,這個流不是流動態的。當你添加了 data 的事件監聽器,加上一個回調函數時,它纔會變成流動態的。在這以後,它就會讀取一小塊數據,而後傳到你的回調函數裏面。
流的實現者決定了 data 事件的觸發頻率,例如 HTTP request 會在讀取到幾 KB 數據的時候觸發 data 事件。 當你從一個文件中讀取數據的時候,你可能會決定當一行被讀完的時候就觸發 data 事件。函數

當沒有數據可讀的時候 (讀到文件尾部時),流就會發送 end 事件。在上面的例子中,咱們監聽了這個事件,當讀完文件的時候,就把數據打印出來。性能

還有另外一種讀取流的方式,你只要在讀到文件尾部前不斷調用流實例中的 read() 方法就能夠了。ui

var fs = require('fs');
var readableStream = fs.createReadStream('file.txt');
var data = '';
var chunk;

readableStream.on('readable', function() {
  while ((chunk = readableStream.read()) != null) {
    data += chunk;
  }
});

readableStream.on('end', function() {
  console.log(data);
});

read() 方法會從內部 buffer 中讀取數據,當沒有數據可讀的時候,它會返回 null
所以,在 while 循環中咱們檢查 read() 是否是返回 null,當它返回 null 的時候,就終止循環。
須要注意的是,當咱們能夠從流中讀取數據的時候,readable 事件就會觸發。編碼

設置編碼

默認狀況下,你從流中讀取到的是 Buffer 對象。若是你要讀取的是字符串的話,這並不適合你。所以,你能夠像下面的例子那樣經過調用 Readable.setEncoding() 來設置流的編碼:

var fs = require('fs');
var readableStream = fs.createReadStream('file.txt');
var data = '';

readableStream.setEncoding('utf8');

readableStream.on('data', function(chunk) {
  data += chunk;
});

readableStream.on('end', function() {
  console.log(data);
});

上面的例子中,咱們把流的編碼設置成 utf8,數據就會被解析成 utf8,回調函數中的 chunk 就會是字符串了。

管道 (Piping)

管道是一個很棒的機制,你不須要本身管理流的狀態就能夠從數據源中讀取數據,而後寫入到目的地中。咱們先看看下面的例子:

var fs = require('fs');
var readableStream = fs.createReadStream('file1.txt');
var writableStream = fs.createWriteStream('file2.txt');

readableStream.pipe(writableStream);

上面的例子利用 pipe() 方法把 file1 的內容寫到 file2 中。由於 pipe() 會幫你管理數據流,你不須要擔憂數據流的速度。這讓 pipe() 變得很是簡潔易用。
須要注意的是,pipe() 會返回目的地的流,所以你能夠很輕易讓多個流連接起來!

連接 (Chaining)

假設有一個歸檔文件,你想要解壓它。有不少方式能夠完成這個任務。但最簡潔的方式是利用管道和連接:

var fs = require('fs');
var zlib = require('zlib');

fs.createReadStream('input.txt.gz')
  .pipe(zlib.createGunzip())
  .pipe(fs.createWriteStream('output.txt'));

首先,咱們經過 input.txt.gz 建立了一個可讀流,而後讓它流向 zlib.createGunzip() 流,它會解壓內容。最後,咱們添加一個可寫流把解壓後的內容寫到另外一個文件中。

其餘方法

咱們已經討論了一些可讀流中重要的概念了,這裏還有一些你須要知道的方法:

  1. Readable.pause() - 這個方法會暫停流的流動。換句話說就是它不會再觸發 data 事件。

  2. Readable.resume() - 這個方法和上面的相反,會讓暫停流恢復流動。

  3. Readable.unpipe() - 這個方法會把目的地移除。若是有參數傳入,它會讓可讀流中止流向某個特定的目的地,不然,它會移除全部目的地。

可寫流 (Writable Streams)

可寫流讓你把數據寫入目的地。就像可讀流那樣,這些也是 EventEmitter,它們也會觸發不一樣的事件。咱們來看看可寫流中會觸發的事件和方法吧。

寫入流

要把數據寫如到可寫流中,你須要在可寫流實例中調用 write() 方法,看看下面的例子:

var fs = require('fs');
var readableStream = fs.createReadStream('file1.txt');
var writableStream = fs.createWriteStream('file2.txt');

readableStream.setEncoding('utf8');

readableStream.on('data', function(chunk) {
  writableStream.write('chunk');
});

上面的代碼很是簡單,它只是從輸入流中讀取數據,而後用 write() 寫入到目的地中。
這個方法返回一個布爾值來表示寫入是否成功。若是返回的是 true 那表示寫入成功,你能夠繼續寫入更多的數據。 若是是 false,那意味着發生了什麼錯誤,你如今不能繼續寫入了。可寫流會觸發一個 drain 事件來告訴你你能夠繼續寫入數據。

寫完數據後

當你不須要在寫入數據的時候,你能夠調用 end() 方法來告訴流你已經完成寫入了。假設 res 是一個 HTTP response 對象,你一般會發送響應給瀏覽器:

res.write('Some Data!!');
res.end();

end() 被調用時,全部數據會被寫入,而後流會觸發一個 finish 事件。注意在調用 end() 以後,你就不能再往可寫流中寫入數據了。例以下面的代碼就會報錯:

res.write('Some Data!!');
res.end();
res.write('Trying to write again'); //Error !

這裏有一些和可寫流相關的重要事件:

  1. error - 在寫入或連接發生錯誤時觸發

  2. pipe - 當可讀流連接到可寫流時,這個事件會觸發

  3. unpipe - 在可讀流調用 unpipe 時會觸發

結論

這些是關於流的基礎知識。流,管道,連接是核心,它們是 Node.js 中最強大的功能。若是使用得當,流能夠幫助你寫出簡潔並且高性能的代碼。

出處

http://scarletsky.github.io/2016/07/01/basics-node-js-streams/

參考資料

https://www.sitepoint.com/basics-node-js-streams/

相關文章
相關標籤/搜索