前言
什麼是流呢?看字面意思,我們可能會想起生活中的水流,電流。
但是流不是水也不是電,它只是描述水和電的流動;所以說流是抽象的。
在node.js中流是一個抽象介面,它不關心檔案內容,只關注是否從檔案中讀到了資料,以及讀到資料之後的處理,接著看:
1.流的概念
- 流是一組有序的,有起點和終點的位元組資料傳輸手段
- 它不關心檔案的整體內容,只關注是否從檔案中讀到了資料;以及讀到資料之後的處理
- 流是一個抽象介面,被node中的很多物件所實現。比如HTTP伺服器request和response物件都是流,TCP伺服器中的socket也是流。
看看官網的介紹:
這裡說了“所有的流都是EventEmitter的例項” 所以流繼承了EventEmitter類。再來看流的型別:
2.流的型別
- Reacable-可讀的流(例如fs.createReadStream())
- Writrable-可寫的流(例如fs.createWriteStream())
- Duplex-可讀寫的流(例如net.Socket)
- Transform-在讀寫的過程中可以修改和變換資料的Duplex流(例如zlib.createDuplex())
3.流的資料模式
流中的資料有兩種模式,二進位制模式和物件模式.
- 二進位制模式, 每個分塊都是buffer或者string物件.
- 物件模式, 流內部處理的是一系列普通物件.
注意:
所有使用 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的簡單實現。
本人水平有限,有錯誤的地方希望指出。