node中的精髓Stream(流)

在前端工程化中產(chǎn)生了很多工具,例如grunt,gulp,webpack,babel...等等侨赡,這些工具都是通過(guò)node中的stream實(shí)現(xiàn)。
在node中stream也是非常非常非常重要的模塊,比如我們常用的console就是基于stream的實(shí)例骇塘,還有net,http等核心模塊都是基于stream來(lái)實(shí)現(xiàn)的,可見stream是多么的重要韩容。

1.什么是stream?

是一種數(shù)據(jù)傳輸手段款违,從一個(gè)地方傳輸?shù)搅硪粋€(gè)地方。

在寫node的時(shí)候會(huì)存在讀取文件群凶,比如現(xiàn)在我們有一個(gè)非常大的文件插爹,50G吧

    const fs = require('fs');
    // test文件50個(gè)G
    fs.readFileSync('./test.text');

這個(gè)時(shí)候需要消耗大量的時(shí)候去讀取這個(gè)文件,然而我們可能關(guān)心的并不是文件所有內(nèi)容请梢,還會(huì)存在直接讀取失敗赠尾。stream就是為了解決這些問(wèn)題而產(chǎn)生,我們讀一些數(shù)據(jù)處理一些數(shù)據(jù)溢陪,當(dāng)讀到所關(guān)心數(shù)據(jù)的時(shí)候萍虽,則可以不再繼續(xù)讀取。

stream翻譯成中文‘流’形真,就像水一樣杉编,從水龍頭流向水杯。

2. Stream模塊

stream繼承于EventEmitter咆霜,擁有事件觸發(fā)和事件監(jiān)聽功能邓馒。主要分為4種基本流類型:

  1. Readable (可讀流)
  2. Writable (可寫流)
  3. Duplex (讀寫流)
  4. Transform (轉(zhuǎn)換流)

    在流中默認(rèn)可操作的類型string和Buffer,如果需要處理其他類型的js值需要傳入?yún)?shù)objectMode: true(默認(rèn)為false)

在流中存在一個(gè)重要的概念,緩存區(qū)蛾坯,就像拿水杯去接水光酣,水杯就是緩存區(qū),當(dāng)水杯滿脉课,則會(huì)關(guān)閉水龍頭救军,等把水杯里面的水消耗完畢,再打開水龍頭去接水倘零。

stream默認(rèn)緩存區(qū)大小為16384(16kb),可以通過(guò)highWaterMark參數(shù)設(shè)置緩存區(qū)大小唱遭,但設(shè)置encoding后,以設(shè)置的字符編碼為單位衡量呈驶。

3. Readable

首先創(chuàng)建一個(gè)可讀流,可接收5個(gè)參數(shù):

  • highWaterMark 緩存區(qū)字節(jié)大小拷泽,默認(rèn)16384
  • encoding 字符編碼,默認(rèn)為null,就是buffer
  • objectMode 是否操作js其他類型 默認(rèn)false
  • read 對(duì)內(nèi)部的_read()方式實(shí)現(xiàn) 子類實(shí)現(xiàn),父類調(diào)用
  • destroy 對(duì)內(nèi)部的_ destroy()方法實(shí)現(xiàn) 子類實(shí)現(xiàn),父類調(diào)用

可讀流中分為2種模式流動(dòng)模式暫停模式司致。

監(jiān)聽data事件拆吆,觸發(fā)流動(dòng)模式,會(huì)源源不斷生產(chǎn)數(shù)據(jù)觸發(fā)data事件:

    const { Readable } = require('stream');
    
    let i = 0;
        
    const rs = Readable({
        encoding: 'utf8',
        // 這里傳入的read方法脂矫,會(huì)被寫入_read()
        read: (size) => {
            // size 為highWaterMark大小
            // 在這個(gè)方法里面實(shí)現(xiàn)獲取數(shù)據(jù)枣耀,讀取到數(shù)據(jù)調(diào)用rs.push([data]),如果沒(méi)有數(shù)據(jù)了羹唠,push(null)結(jié)束流
            if (i < 10) {
                rs.push(`當(dāng)前讀取數(shù)據(jù): ${i++}`);
            } else {
                rs.push(null);
            }
        },
        // 源代碼奕枢,可覆蓋
        destroy(err, cb) {
            rs.push(null);
            cb(err);
        }
    });
        
    rs.on('data', (data) => {
        console.log(data);
        // 每次push數(shù)據(jù)則觸發(fā)data事件
        // 當(dāng)前讀取數(shù)據(jù): 0
        // 當(dāng)前讀取數(shù)據(jù): 1
        // 當(dāng)前讀取數(shù)據(jù): 2
        // 當(dāng)前讀取數(shù)據(jù): 3
        // 當(dāng)前讀取數(shù)據(jù): 4
        // 當(dāng)前讀取數(shù)據(jù): 5
        // 當(dāng)前讀取數(shù)據(jù): 6
        // 當(dāng)前讀取數(shù)據(jù): 7
        // 當(dāng)前讀取數(shù)據(jù): 8
        // 當(dāng)前讀取數(shù)據(jù): 9
    })

監(jiān)聽readable事件娄昆,觸發(fā)暫停模式佩微,當(dāng)流有了新數(shù)據(jù)或到了流結(jié)束之前觸發(fā)readable事件,需要顯示調(diào)用read([size])讀取數(shù)據(jù):

    const { Readable } = require('stream');
        
    let i = 0;
        
    const rs = Readable({
        encoding: 'utf8',
        highWaterMark: 9,
        // 這里傳入的read方法萌焰,會(huì)被寫入_read()
        read: (size) => {
            // size 為highWaterMark大小
            // 在這個(gè)方法里面實(shí)現(xiàn)獲取數(shù)據(jù)哺眯,讀取到數(shù)據(jù)調(diào)用rs.push([data]),如果沒(méi)有數(shù)據(jù)了扒俯,push(null)結(jié)束流
            if (i < 10) {
              // push其實(shí)是把數(shù)據(jù)放入緩存區(qū)
              rs.push(`當(dāng)前讀取數(shù)據(jù): ${i++}`);
            } else {
                rs.push(null);
            }
        }
    });
    
    rs.on('readable', () => {
        const data = rs.read(9);
        console.log(data);
        // 
    })

read([size]) size參數(shù):

  • 不傳代表讀取緩存區(qū)所有數(shù)據(jù)奶卓。
  • 傳入0 填充緩存區(qū), 但返回null
  • size < 當(dāng)前緩存區(qū)數(shù)據(jù) 返回所需數(shù)據(jù)
  • size > 當(dāng)前緩存區(qū)數(shù)據(jù) 返回null 并改變highWaterMark值

這里的緩存區(qū)數(shù)據(jù)不是指highWaterMark,獲取緩存區(qū)數(shù)據(jù)大小rs._readableState.length撼玄。

流的模式可以自由切換: 通過(guò)rs._readableState.flowing的值獲取當(dāng)前狀態(tài)

  • null 初始狀態(tài)
  • false 暫停模式
  • true 流動(dòng)模式

rs.pause()切換到暫停模式 rs.resume()切換到流動(dòng)模式

在可讀流里面還可以監(jiān)聽其他事件:

    
    rs.on('close', () => {
        // 流關(guān)閉時(shí)或文件關(guān)閉時(shí)觸發(fā)
    })
    
    rs.on('end', () => {
        // 在流中沒(méi)有數(shù)據(jù)可供消費(fèi)時(shí)觸發(fā)
    })
    
    rs.on('error', (err) => {
        // 發(fā)生錯(cuò)誤時(shí)候
    })

4. Writable

可寫流可接受參數(shù):

  • highWaterMark 緩存區(qū)字節(jié)大小夺姑,默認(rèn)16384
  • decodeStrings 是否將字符編碼傳入緩沖區(qū)
  • objectMode 是否操作js其他類型 默認(rèn)false
  • write 子類實(shí)現(xiàn),供父類調(diào)用 實(shí)現(xiàn)寫入底層數(shù)據(jù)
  • writev 子類實(shí)現(xiàn)掌猛,供父類調(diào)用 一次處理多個(gè)chunk寫入底層數(shù)據(jù)
  • destroy 可以覆蓋父類方法盏浙,不能直接調(diào)用,銷毀流時(shí)荔茬,父類調(diào)用
  • final 完成寫入所有數(shù)據(jù)時(shí)父類觸發(fā)

在實(shí)現(xiàn)流除了用上面直接傳入?yún)?shù)的方式废膘,還可以用繼承類

class WS extends stream.Writable {
    constructor() {
        super({
            highWaterMark: 1
        });
    }

    _write(chunk, encoding, cb) {
        console.log(this._writableState.length);
        // chunk 為需要寫入的數(shù)據(jù)
        // encoding 字符編碼
        // cb 回調(diào)函數(shù), 如果寫入成功需要調(diào)用cb去執(zhí)行下一次寫入,如果發(fā)生錯(cuò)誤慕蔚,可以cb(new Error([錯(cuò)誤信息]))
        if (chunk.length < 4) {
            fs.writeFileSync('./2.text', chunk, {
                flag: 'a'
            });
            cb();
        } else{
            cb(new Error('超出4個(gè)字節(jié)'));
        }
    }
}

const ws = new WS();

let i = 0;
function next() {
    let flag = true;

    // write() 會(huì)返回boolean false -> 緩存區(qū)沒(méi)滿 true —> 已滿丐黄,需要暫停寫入數(shù)據(jù)
    while(i < 10 && flag) {
        flag = ws.write(`${i++}`);
        console.log('flag', flag);
    }
}

next();

// 當(dāng)所有緩存區(qū)數(shù)據(jù)已經(jīng)成功寫入底層數(shù)據(jù),緩存區(qū)沒(méi)有數(shù)據(jù)了,觸發(fā)drain事件
ws.on('drain', () => {
    console.log('drain');
    // 繼續(xù)寫入緩存區(qū)數(shù)據(jù)
    next();
})

可寫流的end事件,一旦觸發(fā)end事件孔飒,后續(xù)不能再寫入數(shù)據(jù).

    ws.write('start');
    ws.end('end');
    ws.wrtie('test'); // 報(bào)錯(cuò) write after end

finish事件:

    ws.write('start');
    ws.end('end');
    ws.on('finish', () => {
        console.log('調(diào)用end方法后,并且所有數(shù)據(jù)已經(jīng)寫入底層')
    })

cork()與uncork()灌闺,強(qiáng)制所有數(shù)據(jù)先寫入緩存區(qū),直到調(diào)用uncork()或end()坏瞄,這時(shí)一并寫入底層:

    const ws = stream.Writable({
        writev(chunks, encoding, cb) {
            // 這時(shí)chunks為一個(gè)數(shù)組桂对,包含所有的chunk
        
            // 現(xiàn)在length為10
            console.log(chunk.length);
        }
    });
    
    // 寫入數(shù)據(jù)之前,強(qiáng)制寫入數(shù)據(jù)放入緩存區(qū)
    ws.cork();
    
    // 寫入數(shù)據(jù)
    for (let i = 0; i < 10; i++) {
        ws.write(i.toString());
    }
    
    // 寫入完畢惦积,可以觸發(fā)寫入底層
    ws.uncork();

5. Duplex

讀寫流接校,該方法繼承了可寫流和可讀流,但相互之間沒(méi)有關(guān)系,各自獨(dú)立緩存區(qū)蛛勉,擁有Writable和Readable所有方法和事件鹿寻,同時(shí)實(shí)現(xiàn)_read()和_write()方法。

    const fs = require('fs');
    const stream = require('stream');
    
    const duplex = stream.Duplex({
        write(chunk, encoding, cb) {
            console.log(chunk.toString('utf8')); // 寫入
        },
        read() {
            this.push('讀取');
            this.push(null);
        }
    });
    
    console.log(duplex.read(6).toString('utf8')); // 讀取
    
    duplex.write('寫入');

6. Transform

轉(zhuǎn)換流诽凌,這個(gè)流在前端工程化中用到最多毡熏,從一個(gè)地方讀取數(shù)據(jù),轉(zhuǎn)換數(shù)據(jù)后輸出到一個(gè)地方侣诵,該流繼承于Duplex痢法。

    const fs = require('fs');
    const stream = require('stream');
    
    const transform = stream.Transform({
        transform(chunk, encoding, cb){
            // 把數(shù)據(jù)轉(zhuǎn)換成大寫字母,然后push到緩存區(qū)
            this.push(chunk.toString().toUpperCase());
            cb();
        }
    });
    
    transform.write('a');
    
    console.log(transform.read(1).toString()); // A

7. fs快速創(chuàng)建可讀/可寫流

可讀流和可寫流都需要我們?nèi)?shí)現(xiàn)父類的方法杜顺,那么fs這個(gè)模塊幫我們做了這件事情财搁,fs里面實(shí)現(xiàn)了高效并且可靠的可讀/可寫流,提供快速創(chuàng)建流躬络,不再去實(shí)現(xiàn)父類_write()或_read()尖奔。下面我們來(lái)看看如何使用:

    const fs = require('fs');
    
    /**
     * 創(chuàng)建可讀流
     *  
     *  第一個(gè)參數(shù)文件路徑
     * 
     *  第二個(gè)參數(shù)為options
     * 
        flags?: string;      
        encoding?: string;   字符編碼
        fd?: number;     文件打開后的標(biāo)識(shí)符
        mode?: number;    文件的權(quán)限
        autoClose?: boolean;   讀取完畢后,是否自動(dòng)關(guān)閉文件
        start?: number;  從哪個(gè)位置開始讀取
        end?: number;    讀到什么時(shí)候結(jié)束
        highWaterMark?: number;   最高水位線
     */
    const rs = fs.createReadStream('1.text');
    
    rs.on('data', data => {
        console.log(data);
    })
    
    /**
     * 創(chuàng)建可寫流
     *  
     *  第一個(gè)參數(shù)文件路徑
     * 
     *  第二個(gè)參數(shù)為options
     * 
        flags?: string;  
        encoding?: string; 字符編碼
        fd?: number;    文件打開后的標(biāo)識(shí)符
        mode?: number;   文件的權(quán)限
        autoClose?: boolean;  寫入完畢后穷当,是否自動(dòng)關(guān)閉文件
        start?: number;  從什么位置開始寫入
     */
    const ws = fs.createWriteStream('2.text');
    
    ws.write('123');

8. pipe

在流中搭建一條管道提茁,從可讀流中到可寫流。

可讀流中有pipe()方法,在可寫流中可以監(jiān)聽pipe事件馁菜,下面實(shí)現(xiàn)了從可讀流中通過(guò)管道到可寫流:

    const fs = require('fs');
    const stream = require('stream');
    
    const rs = stream.Readable({
        read() {
            this.push(fs.readFileSync('./1.text')); // 文件內(nèi)容 test
            this.push(null);
        }
    });
    
    const ws = stream.Writable({
        write(chunk, encoding, cb) {
            // chunk為test buffer
            fs.writeFileSync('./2.text', chunk.toString());
            cb();
        }
    });
    
    ws.on('pipe', data => {
        // 觸發(fā)pipe事件
        console.log(data);
    });
    
    rs.pipe(ws);

9. 總結(jié)

流分為四種基本類型茴扁,兩種模式。流中的數(shù)據(jù)不是直接寫入或讀取汪疮,有緩存區(qū)的概念峭火。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市铲咨,隨后出現(xiàn)的幾起案子躲胳,更是在濱河造成了極大的恐慌,老刑警劉巖纤勒,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坯苹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡摇天,警方通過(guò)查閱死者的電腦和手機(jī)粹湃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)泉坐,“玉大人为鳄,你說(shuō)我怎么就攤上這事⊥笕茫” “怎么了孤钦?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵歧斟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我偏形,道長(zhǎng)静袖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任俊扭,我火速辦了婚禮队橙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萨惑。我一直安慰自己捐康,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布庸蔼。 她就那樣靜靜地躺著解总,像睡著了一般。 火紅的嫁衣襯著肌膚如雪朱嘴。 梳的紋絲不亂的頭發(fā)上倾鲫,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天粗合,我揣著相機(jī)與錄音萍嬉,去河邊找鬼。 笑死隙疚,一個(gè)胖子當(dāng)著我的面吹牛壤追,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播供屉,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼行冰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了伶丐?” 一聲冷哼從身側(cè)響起悼做,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哗魂,沒(méi)想到半個(gè)月后肛走,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡录别,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年朽色,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片组题。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡葫男,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出崔列,到底是詐尸還是另有隱情梢褐,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站盈咳,受9級(jí)特大地震影響趣效,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜猪贪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一跷敬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧热押,春花似錦西傀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至牙寞,卻和暖如春饺鹃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背间雀。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工悔详, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惹挟。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓茄螃,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親连锯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子归苍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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

  • stream 流是一個(gè)抽象接口,在 Node 里被不同的對(duì)象實(shí)現(xiàn)运怖。例如 request to an HTTP se...
    明明三省閱讀 3,398評(píng)論 1 10
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理拼弃,服務(wù)發(fā)現(xiàn),斷路器摇展,智...
    卡卡羅2017閱讀 134,628評(píng)論 18 139
  • 流是Node中最重要的組件和模式之一吻氧。在社區(qū)里有一句格言說(shuō):讓一切事務(wù)流動(dòng)起來(lái)。這已經(jīng)足夠來(lái)描述在Node中流...
    宮若石閱讀 544評(píng)論 0 0
  • 寫在最前 本次試圖淺析探索Nodejs的Stream模塊中對(duì)于Readable類的一部分實(shí)現(xiàn)(可寫流也差不多)吗购。其...
    Annnnnn閱讀 805評(píng)論 0 0
  • 文/四月麗人 當(dāng)鳥鳴穿透紗簾 我仿佛聽見你歡快的呼喚 淺淺一個(gè)微笑 足以融化一冬的寂寒 有一種陪伴 穿越紅塵的暖 ...
    四月麗人閱讀 100評(píng)論 0 4