淺談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的簡單實現。 本人水平有限,有錯誤的地方希望指出。

相關文章