溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Node.js中Stream是什么

發(fā)布時(shí)間:2020-11-20 10:04:09 來源:億速云 閱讀:153 作者:小新 欄目:web開發(fā)

這篇文章主要介紹了Node.js中Stream是什么,具有一定借鑒價(jià)值,需要的朋友可以參考下。希望大家閱讀完這篇文章后大有收獲。下面讓小編帶著大家一起了解一下。

對(duì)于大部分有后端經(jīng)驗(yàn)的的同學(xué)來說 Stream 對(duì)象是個(gè)再合理而常見的對(duì)象,但對(duì)于前端同學(xué) Stream 并不是那么理所當(dāng)然,github 上甚至有一篇 9000 多 Star 的文章介紹到底什么是 Stream —— stream-handbook(https://link.zhihu.com/?target=https%3A//github.com/substack/stream-handbook)。為了更好的理解 Stream,在這篇文章的基礎(chǔ)上簡(jiǎn)單總結(jié)概括一下。

什么是 Stream

在 Unix 系統(tǒng)中流就是一個(gè)很常見也很重要的概念,從術(shù)語上講流是對(duì)輸入輸出設(shè)備的抽象。

ls | grep *.js

類似這樣的代碼我們?cè)趯懩_本的時(shí)候經(jīng)??梢杂龅?,使用 | 連接兩條命令,把前一個(gè)命令的結(jié)果作為后一個(gè)命令的參數(shù)傳入,這樣數(shù)據(jù)像是水流在管道中傳遞,每個(gè)命令類似一個(gè)處理器,對(duì)數(shù)據(jù)做一些加工,因此 | 被稱為 “管道符號(hào)”。

NodeJS 中 Stream 的幾種類型

從程序角度而言流是有方向的數(shù)據(jù),按照流動(dòng)方向可以分為三種流

  • 設(shè)備流向程序:readable

  • 程序流向設(shè)備:writable

  • 雙向:duplex、transform

NodeJS 關(guān)于流的操作被封裝到了 Stream 模塊,這個(gè)模塊也被多個(gè)核心模塊所引用。按照 Unix 的哲學(xué):一切皆文件,在 NodeJS 中對(duì)文件的處理多數(shù)使用流來完成

  • 普通文件

  • 設(shè)備文件(stdin、stdout)

  • 網(wǎng)絡(luò)文件(http、net)

有一個(gè)很容易忽略的知識(shí)點(diǎn):在 NodeJS 中所有的 Stream 都是 EventEmitter 的實(shí)例。

小例子

我們寫程序忽然需要讀取某個(gè)配置文件 config.json,這時(shí)候簡(jiǎn)單分析一下

  • 數(shù)據(jù):config.json 的內(nèi)容
  • 方向:設(shè)備(物理磁盤文件) -> NodeJS 程序

我們應(yīng)該使用 readable 流來做此事

const fs = require('fs');
const FILEPATH = '...';

const rs = fs.createReadStream(FILEPATH);

通過 fs 模塊提供的 createReadStream() 方法我們輕松的創(chuàng)建了一個(gè)可讀的流,這時(shí)候  config.json 的內(nèi)容從設(shè)備流向程序。我們并沒有直接使用 Stream 模塊,因?yàn)?fs 內(nèi)部已經(jīng)引用了 Stream 模塊,并做了封裝。

有了數(shù)據(jù)后我們需要處理,比如需要寫到某個(gè)路徑 DEST ,這時(shí)候我們遍需要一個(gè) writable 的流,讓數(shù)據(jù)從程序流向設(shè)備。

const ws = fs.createWriteStream(DEST);

兩種流都有了,也就是兩個(gè)數(shù)據(jù)加工器,那么我們?nèi)绾瓮ㄟ^類似 Unix 的管道符號(hào) | 來鏈接流呢?在 NodeJS 中管道符號(hào)就是 pipe() 方法。

const fs = require('fs');
const FILEPATH = '...';

const rs = fs.createReadStream(FILEPATH);
const ws = fs.createWriteStream(DEST);

rs.pipe(ws);

這樣我們利用流實(shí)現(xiàn)了簡(jiǎn)單的文件復(fù)制功能,關(guān)于 pipe() 方法的實(shí)現(xiàn)原理后面會(huì)提到,但有個(gè)值得注意地方:數(shù)據(jù)必須是從上游 pipe 到下游,也就是從一個(gè) readable 流 pipe 到 writable 流。

加工一下數(shù)據(jù)

上面提到了 readable 和 writable 的流,我們稱之為加工器,其實(shí)并不太恰當(dāng),因?yàn)槲覀儾]有加工什么,只是讀取數(shù)據(jù),然后存儲(chǔ)數(shù)據(jù)。

如果有個(gè)需求,把本地一個(gè) package.json 文件中的所有字母都改為小寫,并保存到同目錄下的 package-lower.json 文件下。

這時(shí)候我們就需要用到雙向的流了,假定我們有一個(gè)專門處理字符轉(zhuǎn)小寫的流 lower,那么代碼寫出來大概是這樣的

const fs = require('fs');

const rs = fs.createReadStream('./package.json');
const ws = fs.createWriteStream('./package-lower.json');

rs.pipe(lower).pipe(ws);

這時(shí)候我們可以看出為什么稱 pipe() 連接的流為加工器了,根據(jù)上面說的,必須從一個(gè) readable 流 pipe 到 writable 流:

  • rs -> lower:lower 在下游,所以 lower 需要是個(gè) writable 流
  • lower -> ws:相對(duì)而言,lower 又在上游,所以 lower 需要是個(gè) readable 流

有點(diǎn)推理的趕腳呢,能夠滿足我們需求的 lower 必須是雙向的流,具體使用 duplex 還是 transform 后面我們會(huì)提到。

當(dāng)然如果我們還有額外一些處理動(dòng)作,比如字母還需要轉(zhuǎn)成 ASCII 碼,假定有一個(gè)流 ascii 那么我們代碼可能是

rs.pipe(lower).pipe(acsii).pipe(ws);

同樣 ascii 也必須是雙向的流。這樣處理的邏輯是非常清晰的,那么除了代碼清晰,使用流還有什么好處呢?

為什么應(yīng)該使用 Stream

有個(gè)用戶需要在線看視頻的場(chǎng)景,假定我們通過 HTTP 請(qǐng)求返回給用戶電影內(nèi)容,那么代碼可能寫成這樣

const http = require('http');
const fs = require('fs');

http.createServer((req, res) => {
   fs.readFile(moviePath, (err, data) => {
      res.end(data);
   });
}).listen(8080);

這樣的代碼又兩個(gè)明顯的問題

  • 電影文件需要讀完之后才能返回給客戶,等待時(shí)間超長(zhǎng)

  • 電影文件需要一次放入內(nèi)存中,相似動(dòng)作多了,內(nèi)存吃不消

用流可以講電影文件一點(diǎn)點(diǎn)的放入內(nèi)存中,然后一點(diǎn)點(diǎn)的返回給客戶(利用了 HTTP 協(xié)議的 Transfer-Encoding: chunked 分段傳輸特性),用戶體驗(yàn)得到優(yōu)化,同時(shí)對(duì)內(nèi)存的開銷明顯下降

const http = require('http');

const fs = require('fs');

http.createServer((req, res) => {

   fs.createReadStream(moviePath).pipe(res);

}).listen(8080);

除了上述好處,代碼優(yōu)雅了很多,拓展也比較簡(jiǎn)單。比如需要對(duì)視頻內(nèi)容壓縮,我們可以引入一個(gè)專門做此事的流,這個(gè)流不用關(guān)心其它部分做了什么,只要是接入管道中就可以了

const http = require('http');

const fs = require('fs');

const oppressor = require(oppressor);

http.createServer((req, res) => {

   fs.createReadStream(moviePath)

      .pipe(oppressor)

      .pipe(res);

}).listen(8080);

可以看出來,使用流后,我們的代碼邏輯變得相對(duì)獨(dú)立,可維護(hù)性也會(huì)有一定的改善,關(guān)于幾種流的具體使用方式且聽下回分解。

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享Node.js中Stream是什么內(nèi)容對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,遇到問題就找億速云,詳細(xì)的解決方法等著你來學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI