Node進(jìn)階 ---- Stream(二)

1. Node.js 中有四種基本的流類型

  • Readable - 可讀的流 (例如 fs.createReadStream()).
  • Writable - 可寫的流 (例如 fs.createWriteStream()).
  • Duplex - 可讀寫的流 (例如 net.Socket).
  • Transform - 在讀寫過程中可以修改和變換數(shù)據(jù)的 Duplex 流 (例如 zlib.createDeflate()).

2. 流中的數(shù)據(jù)有兩種模式,二進(jìn)制模式和對象模式

  • 二進(jìn)制模式, 每個(gè)分塊都是buffer或者string對象.

  • 對象模式, 流內(nèi)部處理的是一系列普通對象.

    所有使用 Node.js API 創(chuàng)建的流對象都只能操作 strings 和 Buffer對象愤兵。但是侠驯,通過一些第三方流的實(shí)現(xiàn)秋度,你依然能夠處理其它類型的 JavaScript 值 (除了 null,它在流處理中有特殊意義)。 這些流被認(rèn)為是工作在 “對象模式”(object mode)薯演。 在創(chuàng)建流的實(shí)例時(shí)贸诚,可以通過 objectMode 選項(xiàng)使流的實(shí)例切換到對象模式。試圖將已經(jīng)存在的流切換到對象模式是不安全的销钝。

3. 可讀流的兩種模式

  • 可讀流事實(shí)上工作在下面兩種模式之一:flowingpaused
  • 在 flowing 模式下有咨, 可讀流自動(dòng)從系統(tǒng)底層讀取數(shù)據(jù),并通過 EventEmitter 接口的事件盡快將數(shù)據(jù)提供給應(yīng)用曙搬。
  • 在 paused 模式下摔吏,必須顯式調(diào)用 stream.read() 方法來從流中讀取數(shù)據(jù)片段。
  • 所有初始工作模式為 paused 的 Readable 流纵装,可以通過下面三種途徑切換到 flowing 模式:
    • 監(jiān)聽 'data' 事件
    • 調(diào)用 stream.resume() 方法
    • 調(diào)用 stream.pipe() 方法將數(shù)據(jù)發(fā)送到 Writable
  • 可讀流可以通過下面途徑切換到 paused 模式:
    • 如果不存在管道目標(biāo)(pipe destination)征讲,可以通過調(diào)用 stream.pause() 方法實(shí)現(xiàn)。
    • 如果存在管道目標(biāo)橡娄,可以通過取消 'data' 事件監(jiān)聽诗箍,并調(diào)用 stream.unpipe() 方法移除所有管道目標(biāo)來實(shí)現(xiàn)。

如果 Readable 切換到 flowing 模式挽唉,且沒有消費(fèi)者處理流中的數(shù)據(jù)滤祖,這些數(shù)據(jù)將會(huì)丟失。 比如瓶籽, 調(diào)用了 readable.resume() 方法卻沒有監(jiān)聽 'data' 事件匠童,或是取消了 'data' 事件監(jiān)聽,就有可能出現(xiàn)這種情況塑顺。

4.緩存區(qū)

  • Writable 和 Readable 流都會(huì)將數(shù)據(jù)存儲(chǔ)到內(nèi)部的緩沖器(buffer)中汤求。這些緩沖器可以 通過相應(yīng)的 writable._writableState.getBuffer() 或 readable._readableState.buffer 來獲取。

  • 緩沖器的大小取決于傳遞給流構(gòu)造函數(shù)的 highWaterMark 選項(xiàng)严拒。 對于普通的流扬绪, highWaterMark 選項(xiàng)指定了總共的字節(jié)數(shù)。對于工作在對象模式的流裤唠, highWaterMark 指定了對象的總數(shù)挤牛。

  • 當(dāng)可讀流的實(shí)現(xiàn)調(diào)用stream.push(chunk)方法時(shí),數(shù)據(jù)被放到緩沖器中种蘸。如果流的消費(fèi)者沒有調(diào)用stream.read()方法墓赴, 這些數(shù)據(jù)會(huì)始終存在于內(nèi)部隊(duì)列中竞膳,直到被消費(fèi)。

  • 當(dāng)內(nèi)部可讀緩沖器的大小達(dá)到 highWaterMark 指定的閾值時(shí)竣蹦,流會(huì)暫停從底層資源讀取數(shù)據(jù)顶猜,直到當(dāng)前 緩沖器的數(shù)據(jù)被消費(fèi) (也就是說, 流會(huì)在內(nèi)部停止調(diào)用 readable._read() 來填充可讀緩沖器)痘括。

  • 可寫流通過反復(fù)調(diào)用 writable.write(chunk) 方法將數(shù)據(jù)放到緩沖器长窄。 當(dāng)內(nèi)部可寫緩沖器的總大小小于 highWaterMark 指定的閾值時(shí), 調(diào)用 writable.write() 將返回true纲菌。 一旦內(nèi)部緩沖器的大小達(dá)到或超過 highWaterMark 挠日,調(diào)用 writable.write() 將返回 false 。

  • stream API 的關(guān)鍵目標(biāo)翰舌, 尤其對于 stream.pipe() 方法嚣潜, 就是限制緩沖器數(shù)據(jù)大小,以達(dá)到可接受的程度椅贱。這樣懂算,對于讀寫速度不匹配的源頭和目標(biāo),就不會(huì)超出可用的內(nèi)存大小庇麦。

  • Duplex 和 Transform 都是可讀寫的计技。 在內(nèi)部,它們都維護(hù)了 兩個(gè) 相互獨(dú)立的緩沖器用于讀和寫山橄。 在維持了合理高效的數(shù)據(jù)流的同時(shí)垮媒,也使得對于讀和寫可以獨(dú)立進(jìn)行而互不影響。

5. 可讀流的三種狀態(tài)

在任意時(shí)刻航棱,任意可讀流應(yīng)確切處于下面三種狀態(tài)之一:

  • readable._readableState.flowing = null

  • readable._readableState.flowing = false

  • readable._readableState.flowing = true

  • 若 readable._readableState.flowing 為 null睡雇,由于不存在數(shù)據(jù)消費(fèi)者,可讀流將不會(huì)產(chǎn)生數(shù)據(jù)饮醇。 在這個(gè)狀態(tài)下它抱,監(jiān)聽 'data' 事件,調(diào)用 readable.pipe() 方法朴艰,或者調(diào)用 readable.resume() 方法观蓄, readable._readableState.flowing 的值將會(huì)變?yōu)?true 。這時(shí)呵晚,隨著數(shù)據(jù)生成,可讀流開始頻繁觸發(fā)事件沫屡。

  • 調(diào)用 readable.pause() 方法饵隙, readable.unpipe() 方法, 或者接收 “背壓”(back pressure)沮脖, 將導(dǎo)致 readable._readableState.flowing 值變?yōu)?false金矛。 這將暫停事件流芯急,但 不會(huì) 暫停數(shù)據(jù)生成。 在這種情況下驶俊,為 'data' 事件設(shè)置監(jiān)聽函數(shù)不會(huì)導(dǎo)致 readable._readableState.flowing 變?yōu)?true娶耍。

  • 當(dāng) readable._readableState.flowing 值為 false 時(shí), 數(shù)據(jù)可能堆積到流的內(nèi)部緩存中饼酿。

6.readable

'readable' 事件將在流中有數(shù)據(jù)可供讀取時(shí)觸發(fā)榕酒。在某些情況下,為 'readable' 事件添加回調(diào)將會(huì)導(dǎo)致一些數(shù)據(jù)被讀取到內(nèi)部緩存中故俐。

const readable = getReadableStreamSomehow();
readable.on('readable', () => {
  // 有一些數(shù)據(jù)可讀了
});
  • 當(dāng)?shù)竭_(dá)流數(shù)據(jù)尾部時(shí)想鹰, 'readable' 事件也會(huì)觸發(fā)。觸發(fā)順序在 'end' 事件之前药版。

  • 事實(shí)上辑舷, 'readable' 事件表明流有了新的動(dòng)態(tài):要么是有了新的數(shù)據(jù),要么是到了流的尾部槽片。 對于前者何缓, stream.read() 將返回可用的數(shù)據(jù)。而對于后者还栓, stream.read() 將返回 null碌廓。

    let fs =require('fs');
    let rs = fs.createReadStream('./1.txt',{
      start:3,
      end:8,
      encoding:'utf8',
      highWaterMark:3
    });
    rs.on('readable',function () {
      console.log('readable');
      console.log('rs._readableState.buffer.length',rs._readableState.length);
      let d = rs.read(1);
      console.log('rs._readableState.buffer.length',rs._readableState.length);
      console.log(d);
      setTimeout(()=>{
          console.log('rs._readableState.buffer.length',rs._readableState.length);
      },500)
    });
    

7.流的經(jīng)典應(yīng)用

7.1 行讀取器

7.1.1 換行和回車

  • 以前的打印要每秒可以打印10個(gè)字符,換行城要0.2秒蝙云,正要可以打印2個(gè)字符氓皱。
  • 研制人員就是在每行后面加兩個(gè)表示結(jié)束的字符。一個(gè)叫做"回車"勃刨,告訴打字機(jī)把打印頭定位在左邊界波材;另一個(gè)叫做"換行",告訴打字機(jī)把紙向下移一行身隐。
  • Unix系統(tǒng)里廷区,每行結(jié)尾只有換行"(line feed)",即"\n",
  • Windows系統(tǒng)里面贾铝,每行結(jié)尾是"<回車><換行>"隙轻,即"\r\n"
  • Mac系統(tǒng)里,每行結(jié)尾是"回車"(carriage return)垢揩,即"\r"
  • 在ASCII碼里
    • 換行 \n 10 0A
    • 回車 \r 13 0D

ASCII

7.1.2 代碼

let fs = require('fs');
let EventEmitter = require('events');
let util = require('util');
util.inherits(LineReader, EventEmitter)
fs.readFile('./1.txt',function (err,data) {
    console.log(data);
})
function LineReader(path) {
    EventEmitter.call(this);
    this._rs = fs.createReadStream(path);
    this.RETURN = 0x0D;// \r 13
    this.NEW_LINE = 0x0A;// \n 10
    this.on('newListener', function (type, listener) {
        if (type == 'newLine') {
            let buffer = [];
            this._rs.on('readable', () => {
                let bytes;
                while (null != (bytes = this._rs.read(1))) {
                    let ch = bytes[0];
                    switch (ch) {
                        case this.RETURN:
                            this.emit('newLine', Buffer.from(buffer));
                            buffer.length = 0;
                            let nByte = this._rs.read(1);
                            if (nByte && nByte[0] != this.NEW_LINE) {
                                buffer.push(nByte[0]);
                            }
                            break;
                        case this.NEW_LINE:
                            this.emit('newLine', Buffer.from(buffer));
                            buffer.length = 0;
                            break;
                        default:
                            buffer.push(bytes[0]);
                            break;
                    }
                }
            });
            this._rs.on('end', () => {
                if (buffer.length > 0) {
                    this.emit('newLine', Buffer.from(buffer));
                    buffer.length = 0;
                    this.emit('end');
                }
            })
        }
    });
}

var lineReader = new LineReader('./1.txt');
lineReader.on('newLine', function (data) {
    console.log(data.toString());
}).on('end', function () {
    console.log("end");
})
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末玖绿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子叁巨,更是在濱河造成了極大的恐慌斑匪,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锋勺,死亡現(xiàn)場離奇詭異蚀瘸,居然都是意外死亡狡蝶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門贮勃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贪惹,“玉大人,你說我怎么就攤上這事寂嘉∽嗨玻” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵垫释,是天一觀的道長丝格。 經(jīng)常有香客問我,道長棵譬,這世上最難降的妖魔是什么显蝌? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮订咸,結(jié)果婚禮上曼尊,老公的妹妹穿的比我還像新娘。我一直安慰自己脏嚷,他們只是感情好骆撇,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著父叙,像睡著了一般神郊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上趾唱,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天涌乳,我揣著相機(jī)與錄音,去河邊找鬼甜癞。 笑死夕晓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的悠咱。 我是一名探鬼主播蒸辆,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼析既!你這毒婦竟也來了躬贡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對情侶失蹤眼坏,失蹤者是張志新(化名)和其女友劉穎拂玻,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纺讲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了囤屹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熬甚。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肋坚,靈堂內(nèi)的尸體忽然破棺而出乡括,到底是詐尸還是另有隱情,我是刑警寧澤智厌,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布诲泌,位于F島的核電站,受9級(jí)特大地震影響铣鹏,放射性物質(zhì)發(fā)生泄漏敷扫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一诚卸、第九天 我趴在偏房一處隱蔽的房頂上張望葵第。 院中可真熱鬧,春花似錦合溺、人聲如沸卒密。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哮奇。三九已至,卻和暖如春睛约,著一層夾襖步出監(jiān)牢的瞬間鼎俘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工痰腮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留而芥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓膀值,卻偏偏與公主長得像棍丐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子沧踏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • stream 流是一個(gè)抽象接口歌逢,在 Node 里被不同的對象實(shí)現(xiàn)。例如 request to an HTTP se...
    明明三省閱讀 3,398評(píng)論 1 10
  • 流是Node中最重要的組件和模式之一翘狱。在社區(qū)里有一句格言說:讓一切事務(wù)流動(dòng)起來秘案。這已經(jīng)足夠來描述在Node中流...
    宮若石閱讀 544評(píng)論 0 0
  • 流的基本概念及理解 流是一種數(shù)據(jù)傳輸手段,是有順序的,有起點(diǎn)和終點(diǎn)阱高,比如你要把數(shù)據(jù)從一個(gè)地方傳到另外一個(gè)地方流非常...
    October_yang閱讀 7,675評(píng)論 3 9
  • 春天天氣多變赤惊、氣候潮濕吼旧,人體內(nèi)很容易積聚濕氣,特別回南天的時(shí)候未舟,感覺渾身上下不舒服圈暗。濕氣不僅讓人乏困,整天感覺累累...
    姜艾進(jìn)行到底閱讀 282評(píng)論 0 0
  • ? 一年之前朋友約我一起弄ETC/ETH裕膀,就研究了一下以太坊和它的智能合約员串,以及史上最逆天的區(qū)塊鏈眾籌項(xiàng)目Th...
    繁星若塵啊閱讀 522評(píng)論 0 1