快速認識Node.js中的Stream

AlexShan發表於2018-04-06

StreamNode.js中處理檔案、HTTP請求等功能的重要依賴,接下來我們就瞭解一下什麼是Stream?以及Stream的常見用法。

開始Stream之前,我們先了解一下流的相關概念,將有助於我們理解Stream。在服務端處理檔案以及HTTP請求時,我們經常會用流的方法,來實現檔案的讀寫,資料的傳輸和處理。


0.1 流資料 來自wiki的定義 Wiki

流資料是由不同來源連續生成的資料。
流是一組有序的,有起點和終點的位元組資料傳輸手段。
這些資料應該使用流處理技術逐步處理,而無需訪問所有資料。
另外,應該考慮到在資料中可能發生概念漂移,這意味著流的屬性可能隨時間而改變。
複製程式碼

**0.2 資料流 **

 資料流是一系列數字編碼的相干訊號(資料包或資料包),用於傳輸或接收正在傳輸過程中的資訊。
 一般我們將資料轉換為二進位制或者其它base格式進行傳輸。
複製程式碼

1. 流的定義

流是用於處理Node.js中的流資料的抽象介面。stream模組提供了很多API可供實現stream的介面。Node.js中提供了很多stream的例項,如HTTP serverprocess.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 可讀流的兩種模式

  • flowingpaused

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]);
複製程式碼
  1. 監聽data事件,流切換到流動模式,資料會被儘可能快的讀出
rs.on('data', function (data) {
    console.log(data);
});
複製程式碼
  1. 監聽end事件,該事件會在讀完資料後被觸發
rs.on('end', function () {
    console.log('讀取完成');
});
複製程式碼
  1. 監聽error事件
rs.on('error', function (err) {
    console.log(err);
});
複製程式碼
  1. 監聽open事件
rs.on('open', function () {
    console.log(err);
});
複製程式碼
  1. 監聽close事件
rs.on('close', function () {
    console.log(err);
});
複製程式碼
  1. 設定編碼,與指定{encoding:'utf8'}效果相同,設定編碼
rs.setEncoding('utf8');
複製程式碼
  1. 暫停和恢復觸發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);
複製程式碼

將資料的滯留量限制到一個可接受的水平,以使得不同速度的來源和目標不會淹沒可用記憶體。

相關文章