nodejs中stream(流)學習分享

Meteor發表於2018-02-02

為什麼需要stream

做為一個前端猿,一直對後端每天可以任性的操作伺服器檔案感到羨慕。最近學習node的fs模組玩的也是意猶未盡好不滿足。fs.readFile方法在讀取大檔案時無法勝任,所以流出現了。

stream(流)

我的理解 我們在網上下載資源時,稍微大一點就要下載個把小時。我們的頻寬遠遠不能滿足從服務端把資源整個端走,所以要把資源拆分成小塊,一塊一塊的運輸。資源就像水一樣流到了我們本地。

While it is important for all Node.js users to understand how streams work.(官網說流很重要!)

流的型別

nodejs有四種基本的流型別:

  • 1 Readable 可讀流
  • 2 Writable 可寫流
  • 3 Duplex 可讀可寫流
  • 4 Transform 在讀寫過程中可以修改和變換資料的Duplex流

1.Readable

可讀流的功能是作為上游,提供資料給下游。

建立與使用

let {Readable} = require('stream');
let readable = Readable();
/*
Readable({read(){})
*/
let source = ['a','b','c'];
readable.setEncoding('utf8');
readable._read = function () {
    let data = source.shift()||null;
    console.log('read:',data);
    this.push(data);
}
readable.on('end', function () {
  console.log('end')
})
readable.on('data', function (data) {
    console.log(data)
})
/*
輸出:
read: a
read: b
a
read: c
b
read: null
c
end
*/
複製程式碼

Readable通過例項_read或read方法,在需要資料時,_read()方法會自動呼叫。

  • _read方法結束的標誌是this.push(null)。
  • 當readable繫結data時_read()方法自動呼叫

data事件:當讀入資料時觸發data事件傳入回撥讀到內容。
end事件:“消耗完”,需要滿足兩個條件:

  • 已經呼叫push(null),宣告不會再有任何新的資料產生
  • 快取中的資料也被讀取完

兩種模式(flowing 和 paused)

流動(flowing)模式

flowing 模式下, 可讀流自動從系統底層讀取資料,並通過 EventEmitter 介面的事件儘快將資料提供給應用。
以下條件均可以使readable進入flowing模式:

  • 呼叫resume方法
  • 如果之前未呼叫pause方法進入paused模式,則監聽data事件也會呼叫resume方法。
  • readable.pipe(writable)。pipe中會監聽data事件。

暫停(paused)模式

paused 模式下,必須顯式呼叫 stream.read() 方法來從流中讀取資料片段。
可讀流可以通過下面途徑切換到 paused 模式:

  • 如果不存在管道目標(pipe destination),可以通過呼叫 stream.pause() 方法實現。
  • 如果存在管道目標,可以通過取消 'data' 事件監聽,並呼叫 stream.unpipe() 方法移除所有管道目標來實現。

Readable流程

  1. 通過read或_read方法讀入資料
  2. 將資料push到當前物件
  3. 觸發emit data方法
    nodejs中stream(流)學習分享

深入理解readable
眾所周知node i/o操作通常採用非同步操作。在讀取資料時會把資料寫入快取區。

read方法

在建立流時,會設定一個highWaterMark引數建立最高水位線。

nodejs中stream(流)學習分享
###自定義可讀流

var stream = require('stream');
var util = require('util');
util.inherits(Counter, stream.Readable);
function Counter(options) {
    stream.Readable.call(this, options);
    this._index = 0;
}
Counter.prototype._read = function() {
    if(this._index++<3){
        this.push(this._index+'');
    }else{
        this.push(null);
    }
};
var counter = new Counter();

counter.on('data', function(data){
    console.log("讀到資料: " + data.toString());//no maybe
});
counter.on('end', function(data){
    console.log("讀完了");
});
複製程式碼

2.Writable

可寫流是對資料寫入'目的地'的一種抽象。可寫流的功能是作為下游,消耗上游提供的資料。

let {Writable} = require('stream');
var writable = Writable({
    write: function (data,_,next) {
        console.log(data);
        next&&next();
    }
})
/*
Writable._write(data,_,next)
*/
writable.write('a');
writable.write('b');
writable.write('c');
writable.end();
/*
<Buffer 61>
<Buffer 62>
<Buffer 63>
*/
複製程式碼

write方法

  • write()或_write()的第三個引數next為回撥函式,呼叫next()表示寫入完成,開始寫下一個資料。
  • 必須呼叫end()方法來告訴writable,所有資料均已寫入。

自定義可寫流

var stream = require('stream');
var util = require('util');
util.inherits(Writer, stream.Writable);
let stock = [];
function Writer(opt) {
    stream.Writable.call(this, opt);
}
Writer.prototype._write = function(chunk, encoding, callback) {
    setTimeout(()=>{
        stock.push(chunk.toString('utf8'));
        console.log("增加: " + chunk);
        callback();
    },500)
};
var w = new Writer();
for (var i=1; i<=5; i++){
    w.write("專案:" + i, 'utf8');
}
w.end("結束寫入",function(){
    console.log(stock);
});
複製程式碼

3.管道流

從readable讀出資料,writeable寫入資料

建立與使用

const stream = require('stream')

var index = 0;
const readable = stream.Readable({
    highWaterMark: 2,
    read: function () {
        process.nextTick(() => {
            console.log('push', ++index)
            this.push(index+'');
        })
    }
})

const writable = stream.Writable({
    highWaterMark: 2,
    write: function (chunk, encoding, next) {
        console.log('寫入:', chunk.toString())
    }
})

readable.pipe(writable);
複製程式碼

4.Duplex

const {Duplex} = require('stream');
const inoutStream = new Duplex({
    write(chunk, encoding, callback) {
        console.log(chunk.toString());
        callback();
    },
    read(size) {
        this.push((++this.index)+'');
        if (this.index > 3) {
            this.push(null);
        }
    }
});

inoutStream.index = 0;
process.stdin.pipe(inoutStream).pipe(process.stdout);
複製程式碼

5.Transform

const {Transform} = require('stream');

const upperCase = new Transform({
    transform(chunk, encoding, callback) {
        this.push(chunk.toString().toUpperCase());
        callback();
    }
});

process.stdin.pipe(upperCase).pipe(process.stdout);
/*
實現大小寫轉換
inpt: a
A
*/
複製程式碼

最近在學習nodejs,經過整理寫下學習筆記。歡迎同學溝通學習心得,如有錯誤的地方也歡迎指正。

相關文章