Node.js開發入門—Stream用法詳解

來源:互聯網
上載者:User

Node.js開發入門—Stream用法詳解

Stream是Node.js中非常重要的一個模組,應用廣泛。一個流是一個具備了可讀、可寫或既可讀又可寫能力的介面,通過這些介面,我們可以和磁碟檔案、通訊端、HTTP請求來互動,實現資料從一個地方流動到另一個地方的功能。

所有的流都實現了EventEmitter的介面,具備事件能力,通過發射事件來反饋流的狀態。比如有錯誤發生時會發射“error”事件,有資料可被讀取時發射“data”事件。這樣我們就可以註冊監聽器來處理某個事件,達到我們的目的。

Node.js定義了Readable、Writable、Duplex、Transform四種流,Node.js有各種各樣的模組,分別實現了這些流,我們挑出來一一看一看他們的用法。當然,我們也可以實現自己的流,可以參考Stream的文檔或我們即將提到的這些Node.js裡的實現。

Readable

Readable流提供了一種將外部來源(比如檔案、通訊端等)的資料讀入到應用程式的機制。

可讀的流有兩種模式:流動模式和暫停模式。流動模式下,資料會自動從來源流出,跟不老泉似的,直到來源的資料耗盡。暫停模式下,你得通過stream.read()主動去要資料,你要了它才從來源讀,你不要它就在那兒耗著等你。

可讀流在建立時都是暫停模式。暫停模式和流動模式可以互相轉換。

要從暫停模式切換到流動模式,有下面三種辦法:

給“data”事件關聯了一個處理器 顯式調用resume() 調用pipe()將可讀流橋接到一個可寫流上

要從流動模式切換到暫停模式,有兩種途徑:

如果這個可讀的流沒有橋接可寫流組成管道,直接調用pause() 如果這個可讀的流與若干可寫流組成了管道,需要移除與“data”事件關聯的所有處理器,並且調用unpipe()方法斷開所有管道。

需要注意的是,出於向後相容的原因,移除“data”事件的處理器,可讀流並不會自動從流動模式轉換到暫停模式;還有,對於已組成管道的可讀流,調用pause也不能保證這個流會轉換到暫停模式。

Readable流的一些常見執行個體如下:

用戶端的HTTP響應 服務端的HTTP請求 fs讀取流 zlib流 crypto(加密)流 TCP通訊端 子進程的stdout和stderr process.stdin

Readable流提供了以下事件:

readable:在資料區塊可以從流中讀取的時候發出。它對應的處理器沒有參數,可以在處理器裡調用read([size])方法讀取資料。 data:有資料可讀時發出。它對應的處理器有一個參數,代表資料。如果你只想快快地讀取一個流的資料,給data關聯一個處理器是最方便的辦法。處理器的參數是Buffer對象,如果你調用了Readable的setEncoding(encoding)方法,處理器的參數就是String對象。 end:當資料被讀完時發出。對應的處理器沒有參數。 close:當底層的資源,如檔案,已關閉時發出。不是所有的Readable流都會發出這個事件。對應的處理器沒有參數。 error:當在接收資料中出現錯誤時發出。對應的處理器參數是Error的執行個體,它的message屬性描述了錯誤原因,stack屬性儲存了發生錯誤時的堆棧資訊。

Readable還提供了一些函數,我們可以用它們讀取或操作流:

read([size]):如果你給read方法傳遞了一個大小作為參數,那它會返回指定數量的資料,如果資料不足,就會返回null。如果你不給read方法傳參,它會返回內部緩衝區裡的所有資料,如果沒有資料,會返回null,此時有可能說明遇到了檔案末尾。read返回的資料可能是Buffer對象,也可能是String對象。 setEncoding(encoding):給流設定一個編碼格式,用於解碼讀到的資料。調用此方法後,read([size])方法返回String對象。 pause():暫停可讀流,不再發出data事件 resume():恢複可讀流,繼續發出data事件 pipe(destination,[options]):把這個可讀流的輸出傳遞給destination指定的Writable流,兩個流組成一個管道。options是一個JS對象,這個對象有一個布爾類型的end屬性,預設值為true,當end為true時,Readable結束時自動結束Writable。注意,我們可以把一個Readable與若干Writable連在一起,組成多個管道,每一個Writable都能得到同樣的資料。這個方法返回destination,如果destination本身又是Readable流,就可以級聯調用pipe(比如我們在使用gzip壓縮、解壓縮時就會這樣,馬上會講到)。 unpipe([destination]):連接埠與指定destination的管道。不傳遞destination時,斷開與這個可讀流連在一起的所有管道。

好吧,大概就這些了,我們來舉一個簡單的使用Readable的例子。以fs模組為例吧。

fs.ReadStream實現了stream.Readable,另外還提供了一個“open”事件,你可以給這個事件關聯處理器,處理器的參數是檔案描述符(一個整型數)。

fs.createReadStream(path[, options])用來開啟一個可讀的檔案流,它返回一個fs.ReadStream對象。path參數指定檔案的路徑,可選的options是一個JS對象,可以指定一些選項,類似下面這樣:

{ flags: 'r',  encoding: 'utf8',  fd: null,  mode: 0666,  autoClose: true }

options的flags屬性指定用什麼模式開啟檔案,’w’代表寫,’r’代表讀,類似的還有’r+’、’w+’、’a’等,與Linux下的open函數接受的讀寫入模式類似。encoding指定開啟檔案時使用編碼格式,預設就是“utf8”,你還可以為它指定”ascii”或”base64”。fd屬性預設為null,當你指定了這個屬性時,createReadableStream會根據傳入的fd建立一個流,忽略path。另外你要是想讀取一個檔案的特定地區,可以配置start、end屬性,指定起始和結束(包含在內)的位元組位移。autoClose屬性為true(預設行為)時,當發生錯誤或檔案讀取結束時會自動關閉檔案描述符。

OK,背景差不多了,可以上代碼了,readable.js檔案內容如下:

var fs = require('fs');var readable = fs.createReadStream('readable.js',{  flags: 'r',  encoding: 'utf8',  autoClose: true,  mode: 0666,});readable.on('open', function(fd){  console.log('file was opened, fd - ', fd);});readable.on('readable', function(){  console.log('received readable');});readable.on('data', function(chunk){  console.log('read %d bytes: %s', chunk.length, chunk);});readable.on('end', function(){  console.log('read end');});readable.on('close', function(){  console.log('file was closed.');});readable.on('error', function(err){  console.log('error occured: %s', err.message);});

範例程式碼把readable.js的內容讀取出來,關聯了各種事件,示範了讀取檔案的一般用法。

Writable

Writable流提供了一個介面,用來把資料寫入到目的裝置(或記憶體)中。Writable流的一些常見執行個體:

用戶端的HTTP請求 伺服器的HTTP響應 fs寫入流 zlib流 crypto(加密)流 TCP套結字 子進程的stdin process.stdout和process.stderr

Writable流的write(chunk[,encoding] [,callback])方法可以把資料寫入流中。其中,chunk是待寫入的資料,是Buffer或String對象。這個參數是必須的,其它參數都是可選的。如果chunk是String對象,encoding可以用來指定字串的編碼格式,write會根據編碼格式將chunk解碼成位元組流再來寫入。callback是資料完全重新整理到流中時會執行的回呼函數。write方法返回布爾值,當資料被完全處理後返回true(不一定是完全寫入裝置哦)。

Writable流的end([chunk] [,encoding] [,callback])方法可以用來結束一個可寫流。它的三個參數都是可選的。chunk和encoding的含義與write方法類似。callback是一個可選的回調,當你提供它時,它會被關聯到Writable的finish事件上,這樣當finish事件發射時它就會被調用。

Writable還有setDefaultEncoding等方法,具體可以參考線上文檔。

現在我們來看看Writable公開的事件:

finish: 在end()被調用、所有資料都已被寫入底層裝置後發射。對應的處理器函數沒有參數。 pipe: 當你在Readable流上調用pipe()方法時,Writable流會發射這個事件,對應的處理器函數有一個參數,類型是Readable,指向與它串連的那個Readable流。 unpipe: 當你在Readable流上調用unpipe()方法時,Writable流會發射這個事件,對應的處理器函數有一個參數,類型是Readable,指向與剛與它中斷連線的那個Readable流。 error: 出錯時發射,對應的處理器函數的參數是Error對象。

OK,讓我們來舉兩個小例子。一個是fs的,一個是socket的。

fs.createWriteStream(path[,options])用來建立一個可寫的檔案流,它返回fs.WriteStream對象。第一個參數path是路徑,第二個參數options是JS對象,是可選的,指定建立檔案時的選項,類似:

{ flags: 'w',  defaultEncoding: 'utf8',  fd: null,  mode: 0666 }

defaultEncoding指定預設的文本編碼。前面講fs.createReadStream時提到了。

writeFile.js內容如下:

var fs = require('fs');var writable = fs.createWriteStream('example.txt',{  flags: 'w',  defaultEncoding: 'utf8',  mode: 0666,});writable.on('finish', function(){  console.log('write finished');  process.exit(0);});writable.on('error', function(err){  console.log('write error - %s', err.message);});writable.write('My name is 火雲邪神', 'utf8');writable.end();

很簡單的一個樣本,注意writeFile.js的檔案編碼格式要是UTF8哦。

下面看一個使用TCP通訊端的樣本,echoServer2.js內容如下:

var net = require(net);var server = net.createServer(function(sock){  sock.setEncoding('utf8');  sock.on('pipe', function(src){    console.log('piped');  });  sock.on('error', function(err){    console.log('error - %s', err.message);  });  sock.pipe(sock);});server.maxConnections = 10;server.listen(7, function(){  console.log('echo server bound at port - 7');});

上面的echoServer功能和我們之前在Node.js開發入門——通訊端(socket)編程中的echoServer一樣。不同的是,這裡使用了pipe方法,而那個版本監聽data事件,調用write方法將收到的資料回寫給用戶端。

sock.Socket是Duplex流,既實現了Readable又實現了Writable,所以,sock.pipe(sock)是正確的調用。

常見的Duplex流有:

TCP socket zlib crypto

Duplex是Readable和Writable的合體。

Transform

Transform擴充了Duplex流,它會修改你使用Writable介面寫入的資料,當你用Readable介面來讀時,資料已經發生了變化。

比較常見的Transform流有:

zlib crypto

好啦,我們舉一個簡單的樣本,使用zlib模組來壓縮和解壓縮。樣本檔案是zlibFile.js,內容如下:

var zlib = require(zlib);var gzip = zlib.createGzip();var fs = require('fs');var inFile = fs.createReadStream('readable.js');var outGzip = fs.createWriteStream('readable.gz');//inFile - Readable//gzip - Transform(Readable && Writable)//outFile - WritableinFile.pipe(gzip).pipe(outGzip);setTimeout(function(){  var gunzip = zlib.createUnzip({flush: zlib.Z_FULL_FLUSH});  var inGzip = fs.createReadStream('readable.gz');  var outFile = fs.createWriteStream('readable.unzipped');  inGzip.pipe(gunzip).pipe(outFile);}, 5000);

上面的樣本比較簡單,使用了zlib模組,文檔在這裡:https://nodejs.org/api/zlib.html。

接下來我們來實現一個Transform流,把輸入資料中的小寫字母轉換為大寫字母。我們代碼在upperTransform.js裡,內容如下:

var fs = require('fs');var util = require('util');var stream = require('stream');util.inherits(UpperTransform, stream.Transform);function UpperTransform(opt){  stream.Transform.call(this, opt);}UpperTransform.prototype._transform = function(chunk, encoding, callback){  var data = new Buffer(chunk.length);  var str = chunk.toString('utf8');  for(var i = 0, offset=0; i < str.length; i++){    if(/^[a-z]+$/.test(str[i])){      offset += data.write(str[i].toUpperCase(), offset);    }else{      offset += data.write(str[i], offset);    }  }  this.push(data);  callback();}UpperTransform.prototype._flush = function(cb){  cb();}var upper = new UpperTransform();var inFile = fs.createReadStream('example.txt');inFile.setEncoding('utf8');var outFile = fs.createWriteStream('exampleUpper.txt',{defaultEncoding: 'utf8'});inFile.pipe(upper).pipe(outFile);

為了實現自訂的Transform,需要先繼承Transform流的功能。實現這一點最簡單的辦法就是使用util模組的inherits()方法,然後在你的構造器裡調用使用call方法把父物件應用到當前對象上。代碼就是下面這部分:

util.inherits(UpperTransform, stream.Transform);function UpperTransform(opt){  stream.Transform.call(this, opt);}

繼承了stream.Transform之後,實現_transform和_flush即可。在_transform裡,我們先建立了一個緩衝區,然後把傳入的資料(chunk)轉換成字串(寫死為utf8了),接著遍曆字串,遇見小寫字母就轉換一下,寫入建立的緩衝區裡,完成轉換後,調用push方法,把轉換後的資料加到內部的資料隊列中。

其它的就比較簡單了。注意,作為樣本,我們只轉換utf8編碼的文字檔。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.