淺談node.js中的stream(流)

acdseen發表於2019-02-16

前言

什麼是流呢?看字面意思,我們可能會想起生活中的水流,電流。
但是流不是水也不是電,它只是描述水和電的流動;所以說流是抽象的。
在node.js中流是一個抽象介面,它不關心檔案內容,只關注是否從檔案中讀到了資料,以及讀到資料之後的處理,接著看:

1.流的概念

  • 流是一組有序的,有起點和終點的位元組資料傳輸手段
  • 它不關心檔案的整體內容,只關注是否從檔案中讀到了資料;以及讀到資料之後的處理
  • 流是一個抽象介面,被node中的很多物件所實現。比如HTTP伺服器request和response物件都是流,TCP伺服器中的socket也是流。

看看官網的介紹:
圖片描述

這裡說了“所有的流都是EventEmitter的例項” 所以流繼承了EventEmitter類。再來看流的型別:

2.流的型別

3.流的資料模式

流中的資料有兩種模式,二進位制模式和物件模式.

  1. 二進位制模式, 每個分塊都是buffer或者string物件.
  2. 物件模式, 流內部處理的是一系列普通物件.

注意:
所有使用 Node.js API 建立的流物件都只能操作 strings 和 Buffer物件。但是,通過一些第三方流的實現,你依然能夠處理其它型別的 JavaScript 值 (除了 null,它在流處理中有特殊意義)。 這些流被認為是工作在 “物件模式”(object mode)。 在建立流的例項時,可以通過 objectMode 選項使流的例項切換到物件模式。試圖將已經存在的流切換到物件模式是不安全的。
說了那麼多,現在開始寫流:

4.可讀流(createReadStream)

4.1 建立可讀流

圖片描述

這裡說說流程:

  • 首先可讀流會開啟檔案,觸發open事件
  • 接著開始讀取,瘋狂的觸發data事件
  • 然後讀完了,觸發end事件
  • 最後因為設定了autoClose為true,自動關閉檔案觸發close事件

可以看到,data事件不斷的被觸發,當我們想讀一下停一下時怎麼辦呢?
這裡就需要聊聊可讀流的兩種模式:

4.2 可讀流的兩種模式

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

初始工作模式為 paused 的 Readable 流,可以通過下面三種途徑切換到 flowing 模式:

1. 監聽 `data` 事件
2. 呼叫 stream.resume() 方法

圖片描述

3.呼叫 stream.pipe() 方法將資料傳送到 Writable

注意:
如果 Readable 切換到 flowing 模式,且沒有消費者處理流中的資料,這些資料將會丟失。 比如, 呼叫了 readable.resume() 方法卻沒有監聽 `data` 事件,或是取消了 `data` 事件監聽,就有可能出現這種情況

在 paused 模式下,必須顯式呼叫 stream.read() 方法來從流中讀取資料片段。
在可讀流`readable`事件裡我們就必須呼叫stream.read()方法。

4.3 可讀流`readable`事件

這裡需要明白三點:
先建立一個1.txt
圖片描述

1.當我只要建立一個流 就會先把快取區 填滿,等待著你自己消費
2.當你消費小於 最高水位線時 會自動新增highWaterMark這麼多資料
圖片描述

3.如果當前快取區被清空後會再次觸發readable事件
圖片描述

4.4 readable原理圖

  • 用Readable建立物件readable後,便得到了一個可讀流。
  • 如果實現_read方法,就將流連線到一個底層資料來源。
  • 流通過呼叫_read向底層請求資料,底層再呼叫流的push方法將需要的資料傳遞過來。
  • 當readable連線了資料來源後,下游便可以呼叫readable.read(n)向流請求資料,同時監聽readable的data事件來接收取到的資料。

圖片描述

5.可寫流(createWriteStream)

5.1 建立可寫流

5.1.1 write方法

圖片描述

5.1.2 end方法

表明接下來沒有資料要被寫入 Writable 通過傳入可選的 chunk 和 encoding 引數,可以在關閉流之前再寫入一段資料 如果傳入了可選的 callback 函式,它將作為 `finish` 事件的回撥函式

圖片描述

5.1.3 drain方法

drain事件的觸發條件,必須滿足兩個條件:
1.當前快取區滿了,不能再寫了
2.快取區滿了後被清空了,才會觸發drain事件
圖片描述

6.pipe方法(管道)

我們在開發中可能會遇到,要把可讀流讀出的資料需要放到可寫流中去寫入到檔案裡面,這時就可以用pipe方法

6.1 pipe的原理

pipe方法的原理很簡單,就是讀一點,寫一點,上程式碼

let fs = require(`fs`);
let ws = fs.createWriteStream(`./2.txt`);
let rs = fs.createReadStream(`./1.txt`);
rs.on(`data`, data => {
    var flag = ws.write(data);
    if(!flag)
    rs.pause();
});
ws.on(`drain`, () => {
    rs.resume();
});
rs.on(`end`,  () => {
    ws.end();
});

6.2 pipe的用法

let from = fs.createReadStream(`./1.txt`);
let to = fs.createWriteStream(`./2.txt`);
from.pipe(to);

6.3 unpipe方法

readable.unpipe()方法將之前通過stream.pipe()方法繫結的流分離

let fs = require(`fs`);
let from = fs.createReadStream(`./1.txt`);
let to = fs.createWriteStream(`./2.txt`);
from.pipe(to);
setTimeout(() => {
    console.log(`關閉向2.txt的寫入`);
    from.unpipe(writable);
    console.log(`手動關閉可寫流`);
    to.end();
}, 1000);

7.自定義流

我們可以引入stream模組,想實現什麼流 就繼承這個流。

7.1 自定義可讀流

  • 我們可以直接把供使用的資料push出去。
  • 當push一個null物件就意味著我們想發出訊號——這個流沒有更多資料了

圖片描述

7.2 自定義可寫流

為了實現可寫流,我們需要使用流模組中的Writable建構函式。 我們只需給Writable建構函式傳遞一些選項並建立一個物件。唯一需要的選項是write函式,該函式揭露資料塊要往哪裡寫。
圖片描述

7.3 實現雙工流(duplex)

有了雙工流,我們可以在同一個物件上同時實現可讀和可寫,就好像同時繼承這兩個介面。 重要的是雙工流的可讀性和可寫性操作完全獨立於彼此。
net中的Socket就是一個duplex雙工流
圖片描述

8.結束語

說到這裡,我想大家應該大致瞭解了node.js裡面的流。
之前說過在node裡流還是很重要的,http裡的request和response都是流。
在下一篇文章我會寫一個readStream和writeStream的簡單實現。
本人水平有限,有錯誤的地方希望指出。

相關文章