轉帖|行業資訊|編輯:黃竹雯|2016-07-04 18:09:52.000|閱讀 247 次
概述:Node.js 天生異步和事件驅動,非常適合處理 I/O 相關的任務。如果你在處理應用中 I/O 相關的操作,你可以利用 Node.js 中的流(stream)。因此,我們先具體看看流,理解一下它們是怎么簡化 I/O 操作的吧。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
Node.js 天生異步和事件驅動,非常適合處理 I/O 相關的任務。如果你在處理應用中 I/O 相關的操作,你可以利用 Node.js 中的流(stream)。因此,我們先具體看看流,理解一下它們是怎么簡化 I/O 操作的吧。
流是 unix 管道,讓你可以很容易地從數據源讀取數據,然后流向另一個目的地。
簡單來說,流不是什么特別的東西,它只是一個實現了一些方法的 EventEmitter 。根據它實現的方法,流可以變成可讀流(Readable),可寫流(Writable),或者雙向流(Duplex,同時可讀可寫)。
可讀流能讓你從一個數據源讀取數據,而可寫流則可以讓你往目的地寫入數據。
如果你已經用過 Node.js,你很可能已經遇到過流了。
例如,在一個 Node.js 的 HTTP 服務器里面, request 是一個可讀流, response 是一個可寫流。
你也可能用過 fs 模塊,它能幫你處理可讀可寫流。
現在讓你學一些基礎,理解不同類型的流。本文會討論可讀流和可寫流,雙向流超出了本文的討論范圍,我們不作討論。
我們可以用可讀流從一個數據源中讀取數據,這個數據源可以是任何東西,例如系統中的一個文件,內存中的 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() 方法就可以了。
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() 流,它會解壓內容。最后,我們添加一個可寫流把解壓后的內容寫到另一個文件中。
我們已經討論了一些可讀流中重要的概念了,這里還有一些你需要知道的方法:
可寫流讓你把數據寫入目的地。就像可讀流那樣,這些也是 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 !
這里有一些和可寫流相關的重要事件:
更多精彩內容和產品推薦,請咨詢!
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn