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
如今讓你學一些基礎,理解不一樣類型的流。本文會討論可讀流和可寫流,雙向流超出了本文的討論範圍,咱們不做討論。瀏覽器
咱們能夠用可讀流從一個數據源中讀取數據,這個數據源能夠是任何東西,例如系統中的一個文件,內存中的 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
就會是字符串了。
管道是一個很棒的機制,你不須要本身管理流的狀態就能夠從數據源中讀取數據,而後寫入到目的地中。咱們先看看下面的例子:
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()
會返回目的地的流,所以你能夠很輕易讓多個流連接起來!
假設有一個歸檔文件,你想要解壓它。有不少方式能夠完成這個任務。但最簡潔的方式是利用管道和連接:
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()
流,它會解壓內容。最後,咱們添加一個可寫流把解壓後的內容寫到另外一個文件中。
咱們已經討論了一些可讀流中重要的概念了,這裏還有一些你須要知道的方法:
Readable.pause()
- 這個方法會暫停流的流動。換句話說就是它不會再觸發 data
事件。
Readable.resume()
- 這個方法和上面的相反,會讓暫停流恢復流動。
Readable.unpipe()
- 這個方法會把目的地移除。若是有參數傳入,它會讓可讀流中止流向某個特定的目的地,不然,它會移除全部目的地。
可寫流讓你把數據寫入目的地。就像可讀流那樣,這些也是 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 !
這裏有一些和可寫流相關的重要事件:
error
- 在寫入或連接發生錯誤時觸發
pipe
- 當可讀流連接到可寫流時,這個事件會觸發
unpipe
- 在可讀流調用 unpipe
時會觸發
這些是關於流的基礎知識。流,管道,連接是核心,它們是 Node.js 中最強大的功能。若是使用得當,流能夠幫助你寫出簡潔並且高性能的代碼。
http://scarletsky.github.io/2016/07/01/basics-node-js-streams/