初識 Node.js stream

xiaopi發表於2018-04-07

什麼是 stream

stream(流)是一種處理流資料的抽象介面,Node.js 提供了 stream 模組用來支援這種資料結構。通過 stream 的 API 可以構建實現流介面的物件。

Node.js 很多地方都用到了流,比如 http 的 request 物件和 fs 的讀取流等。

stream 有什麼用

就拿 fs.readFile 來說,readFile 方法會一次性把所有資料讀取到記憶體中,如果檔案體積過大,那麼會佔用過多記憶體。

stream 的特點是將資料放到緩衝器(Buffer)中,有序的讀取部分資料到記憶體中,這樣就解決了記憶體佔用的問題。

stream 的分類

  • 可讀流(Readable)
  • 可寫流(Writable)
  • 既可讀又可寫流(Duplex)

可讀流(Readable Stream)

1、兩種工作模式

  • flowing(流動)模式:可讀流自動從系統底層讀取資料,並通過 EventEmitter 介面的事件儘快將資料提供給應用。
  • paused(暫停)模式:必須顯式呼叫 stream.read() 方法來從流中讀取資料片段。

2、建立可讀流

以 fs 模組為例,下面是一個簡單的檔案讀取流:

let fs = require('fs');
// 假設當前目錄下有一個 example.txt 檔案,內容為:Hello Node.js!
let rs = fs.createReadStream('example.txt', 'utf8'); // 建立可讀流
複製程式碼

3、事件

3.1 data 事件

讀取流預設情況下是 paused 模式,可以通過監聽 data 事件將其轉換為 flowing 模式,在 data 事件的回撥函式中可以讀取到流的資訊。

let fs = require('fs');
let rs = fs.createReadStream('example.txt', 'utf8'); 

rs.on('data', chunk => {
    console.log(chunk);
});
// -> Hello Node.js!
複製程式碼

3.2 end 事件

當資料全部讀取完畢,end 事件會被觸發

let fs = require('fs');
let rs = fs.createReadStream('example.txt', 'utf8');
rs.on('data', chunk => {
    console.log(chunk);
});
rs.on('end', () => {
    console.log('END');
});
// -> Hello Node.js!
// -> END
複製程式碼

3.3 open、close、error 事件

open 事件在檔案讀取前被呼叫,close 事件在 end 事件之後被呼叫,error 事件顧名思義在讀取出錯時被呼叫。

let fs = require('fs');
let rs = fs.createReadStream('example.txt', 'utf8');

rs.on('data', chunk => {
    console.log(chunk);
});
rs.on('end', () => {
    console.log('END');
});
rs.on('open', () => {
    console.log('OPEN');
});
rs.on('close', () => {
    console.log('CLOSE');
});
rs.on('error', (err) => {
    console.log(err);
});
// -> OPEN!
// -> Hello Node.js!
// -> END
// -> CLOSE
複製程式碼

4、方法

4.1 pause() 方法

flowing 模式會不斷的讀取資料,直到資料全部讀取完畢。

let fs = require('fs');
// Node.js 的緩衝器預設為 64k,為了演示 pause() 方法,假設有一個大小為 180k 的 jpg 圖片
let rs = fs.createReadStream('image.jpg');
let count = 0;

rs.on('data', chunk => {
    console.log('讀取' + (++count) + '次');
});
// -> 讀取1次
// -> 讀取2次
// -> 讀取3次
複製程式碼

pause() 方法可以使 flowing 模式的流停止觸發 data 事件,從而切出 flowing 模式。

let fs = require('fs');
// 假設有一個大小為 180k 的 jpg 圖片
let rs = fs.createReadStream('image.jpg');
let count = 0;

rs.on('data', chunk => {
    console.log('讀取' + (++count) + '次');
    rs.pause();
});
// -> 讀取1次
複製程式碼

上面的程式碼只讀取了一次,遇到 rs.pause() 就暫停了。

4.2 resume() 方法

與 pause() 相對應,恢復事件的傳送。

let fs = require('fs');
// 假設有一個大小為 180k 的 jpg 圖片
let rs = fs.createReadStream('image.jpg');
let count = 0;

rs.on('data', chunk => {
    console.log('讀取' + (++count) + '次');
    rs.pause();
});
setInterval(function () {
    rs.resume();
}, 1000);
// -> 讀取1次
// 1s 後
// -> 讀取2次
// 1s 後
// -> 讀取3次
複製程式碼

可寫流(Writable Stream)

1、建立可寫流

let fs = require('fs');
let rs = fs.createWriteStream('example2.txt', 'utf8'); // 建立寫入流
// 在當前目錄下建立一個空文字檔案 example2.txt
複製程式碼

2、方法

2.1 write() 方法

寫入資料,當資料必須要在內部被緩衝時,返回 false。

let fs = require('fs');
let rs = fs.createWriteStream('example2.txt', 'utf8'); // 建立寫入流
// 在當前目錄下建立一個空文字檔案 example2.txt
rs.write('人家四月芳菲盡,山寺桃花始盛開。');
rs.write('長恨春歸無覓處,不知轉入此中來。');
複製程式碼

要以流的形式寫入檔案,只需要不斷呼叫 write() 方法。

2.2 end() 方法

呼叫 end(),寫完之後就終止新的寫入了。

let fs = require('fs');
let rs = fs.createWriteStream('example2.txt', 'utf8');

rs.write('人家四月芳菲盡,山寺桃花始盛開。');
rs.write('長恨春歸無覓處,不知轉入此中來。');
rs.end();
複製程式碼

3、事件

  • drain 事件:表明資料還沒有寫完,呼叫 write() 方法返回 false。
  • finish 事件:在呼叫了 end() 方法,且緩衝區的資料都已經寫完時被觸發。

相關文章