Stream
是Node.js
中處理檔案、HTTP請求等功能的重要依賴,接下來我們就瞭解一下什麼是Stream
?以及Stream
的常見用法。
開始
Stream
之前,我們先了解一下流的相關概念,將有助於我們理解Stream
。在服務端處理檔案以及HTTP請求時,我們經常會用流的方法,來實現檔案的讀寫,資料的傳輸和處理。
0.1 流資料 來自wiki的定義 Wiki
流資料是由不同來源連續生成的資料。
流是一組有序的,有起點和終點的位元組資料傳輸手段。
這些資料應該使用流處理技術逐步處理,而無需訪問所有資料。
另外,應該考慮到在資料中可能發生概念漂移,這意味著流的屬性可能隨時間而改變。
複製程式碼
**0.2 資料流 **
資料流是一系列數字編碼的相干訊號(資料包或資料包),用於傳輸或接收正在傳輸過程中的資訊。
一般我們將資料轉換為二進位制或者其它base格式進行傳輸。
複製程式碼
1. 流的定義
流是用於處理Node.js中的流資料的抽象介面。stream
模組提供了很多API可供實現stream的介面。Node.js中提供了很多stream的例項,如HTTP server
和process.stdout
。在它們中能充分體驗到stream的應用。
1.1 引用stream模組:
const stream = require('stream');
2. 流的分類
Node.js中有4中基本流的型別:
- 可讀流
Readable
:可以讀的流fs.createReadStream()
- 可寫流
Writable
: 可以寫的流fs.createWriteStream()
- 雙工流
Duplex
: 既可讀,又可以寫net.Socket
- 轉換流
Transform
: 在寫入和讀取資料時可以修改或轉換資料的雙工資料流,zlib.createDeflate()
3. 流中的資料模式
- 二進位制模式,每個分塊都是buffer或者string物件
- 物件模式,流內部處理的是一系列普通物件
4. 快取區Buffer
Writable 和 Readable 流都會將資料儲存到內部的緩衝器(buffer)中。
這些緩衝器可以 通過相應的 writable._writableState.getBuffer() 或
readable._readableState.buffer 來獲取。
當內部可讀緩衝器的大小達到highWaterMark指定的閾值時,
流會暫停從底層資源讀取資料,直到當前緩衝器的資料被消費 (也就是說,
流會在內部停止呼叫 readable._read()來填充可讀緩衝器)。
可寫流通過反覆呼叫 writable.write(chunk)
方法將資料放到緩衝器。當內部可寫緩衝器的總大小小於highWaterMark指定的閾
值時, 呼叫 writable.write() 將返回true。一旦內部緩衝器的大小達到或超過
highWaterMark ,呼叫writable.write() 將返回 false 。
複製程式碼
5. 可讀流
5.1 可讀流的兩種模式
flowing
和paused
5.2 可讀流的三種狀態
- readable._readableState.flowing = null
- readable._readableState.flowing = false
- readable._readableState.flowing = true
5.3 可讀流的常用方法
可讀流,實現了stream.Readable介面的物件,將物件資料讀取為流資料,當監聽data事件後,開始發射資料
5.3.1 建立可讀流
var rs = fs.createReadStream(path,[options]);
複製程式碼
- 監聽data事件,流切換到流動模式,資料會被儘可能快的讀出
rs.on('data', function (data) {
console.log(data);
});
複製程式碼
- 監聽end事件,該事件會在讀完資料後被觸發
rs.on('end', function () {
console.log('讀取完成');
});
複製程式碼
- 監聽error事件
rs.on('error', function (err) {
console.log(err);
});
複製程式碼
- 監聽open事件
rs.on('open', function () {
console.log(err);
});
複製程式碼
- 監聽close事件
rs.on('close', function () {
console.log(err);
});
複製程式碼
- 設定編碼,與指定{encoding:'utf8'}效果相同,設定編碼
rs.setEncoding('utf8');
複製程式碼
- 暫停和恢復觸發data,通過pause()方法和resume()方法
rs.on('data', function (data) {
rs.pause();
console.log(data);
});
setTimeout(function () {
rs.resume();
},2000);
複製程式碼
6. 可寫流
實現了stream.Writable介面的物件來將流資料寫入到物件中
fs.createWriteStream = function(path, options) {
return new WriteStream(path, options);
};
util.inherits(WriteStream, Writable);
複製程式碼
6.1 建立可寫流
var ws = fs.createWriteStream(path,[options]);
path寫入的檔案路徑
options
flags開啟檔案要做的操作,預設為'w'
encoding預設為utf8
highWaterMark寫入快取區的預設大小16kb
複製程式碼
6.2 write方法
ws.write(chunk,[encoding],[callback]);
chunk寫入的資料buffer/string encoding編碼格式chunk為字串時有用,可選 callback 寫入成功後的回撥 返回值為布林值,系統快取區滿時為false,未滿時為true
6.3 end方法
ws.end(chunk,[encoding],[callback]);
複製程式碼
表明接下來沒有資料要被寫入 Writable 通過傳入可選的 chunk 和 encoding 引數,可以在關閉流之前再寫入一段資料 如果傳入了可選的 callback 函式,它將作為 'finish' 事件的回撥函式
6.4 drain方法
當一個流不處在 drain 的狀態, 對 write() 的呼叫會快取資料塊, 並且返回 false。 一旦所有當前所有快取的資料塊都排空了(被作業系統接受來進行輸出), 那麼 'drain' 事件就會被觸發
let fs = require('fs');
let ws = fs.createWriteStream('./2.txt',{
flags:'w',
encoding:'utf8',
highWaterMark:3
});
let i = 10;
function write(){
let flag = true;
while(i&&flag){
flag = ws.write("1");
i--;
console.log(flag);
}
}
write();
ws.on('drain',()=>{
console.log("drain");
write();
});
複製程式碼
6.4 finish方法
在呼叫了 stream.end() 方法,且緩衝區資料都已經傳給底層系統之後, 'finish' 事件將被觸發。
var writer = fs.createWriteStream('./2.txt');
for (let i = 0; i < 100; i++) {
writer.write(`hello, ${i}!\n`);
}
writer.end('結束\n');
writer.on('finish', () => {
console.error('所有的寫入已經完成!');
});
複製程式碼
7 Pipe 方法
7.1 pipe方法的原理
var fs = require('fs');
var ws = fs.createWriteStream('./2.txt');
var rs = fs.createReadStream('./1.txt');
rs.on('data', function (data) {
var flag = ws.write(data);
if(!flag){
rs.pause();
}
});
ws.on('drain', function () {
rs.resume();
});
rs.on('end', function () {
ws.end();
});
複製程式碼
readStream.pipe(writeStream);
var from = fs.createReadStream('./1.txt');
var to = fs.createWriteStream('./2.txt');
from.pipe(to);
複製程式碼
將資料的滯留量限制到一個可接受的水平,以使得不同速度的來源和目標不會淹沒可用記憶體。