什麼是 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() 方法,且緩衝區的資料都已經寫完時被觸發。