Nodejs學習筆記之Stream模組

2022-04-07 19:03:48 字數 4393 閱讀 2110

nodejs的fs模組並沒有提供乙個copy的方法,但我們可以很容易的實現乙個,這種方式是把檔案內容全部讀入記憶體,然後再寫入檔案,對於小型的文字檔案,這沒有多大問題,比如grunt-file-copy就是這樣實現的。

一,開篇分析

流是乙個抽象介面,被 node 中的很多物件所實現。比如對乙個 http 伺服器的請求是乙個流,stdout 也是乙個流。流是可讀,可寫或兼具兩者的。

最早接觸stream是從早期的unix開始的, 數十年的實踐證明stream 思想可以很簡單的開發出一些龐大的系統。

在unix裡,stream是通過 "|" 實現的。在node中,作為內建的stream模組,很多核心模組和三方模組都使用到。

和unix一樣,node stream主要的操作也是.pipe(),使用者可以使用反壓力機制來控制讀和寫的平衡。

stream 可以為開發者提供可以重複使用統一的介面,通過抽象的stream介面來控制stream之間的讀寫平衡。

乙個tcp連線既是可讀流,又是可寫流,而http連線則不同,乙個http request物件是可讀流,而http response物件則是可寫流。

流的傳輸過程預設是以buffer的形式傳輸的,除非你給他設定其他編碼形式,以下是乙個例子:

複製****如下:

res.end("hello,大熊!") ;

執行後會有亂碼出現,原因就是沒有設定指定的字符集,比如:「utf-8」 。

修改一下就好:

複製****如下:

res.end("hello,大熊!") ;

執行結果:

複製****如下:

**可以實現需要的功能,但是服務在傳送檔案資料之前需要快取整個檔案資料到記憶體,如果"data.txt"檔案很 

大並且併發量很大的話,會浪費很多記憶體。因為使用者需要等到整個檔案快取到記憶體才能接受的檔案資料,這樣導致 

使用者體驗相當不好。不過還好(req,res)兩個引數都是stream,這樣我們可以用fs.createreadstream()代替fs.readfile()。如下:

複製****如下:

.pipe()方法監聽fs.createreadstream()的'data' 和'end'事件,這樣"data.txt"檔案就不需要快取整 

個檔案,當客戶端連線完成之後馬上可以傳送乙個資料塊到客戶端。使用.pipe()另乙個好處是可以解決當客戶 

端延遲非常大時導致的讀寫不平衡問題。

有五種基本的stream:readable,writable,transform,duplex,and "classic」 。(具體使用請自己查閱api)

二,例項引入

當記憶體中無法一次裝下需要處理的資料時,或者一邊讀取一邊處理更加高效時,我們就需要用到資料流。nodejs中通過各種stream來提供對資料流的操作。

以大檔案拷貝程式為例,我們可以為資料來源建立乙個唯讀資料流,示例如下:

複製****如下:

var rs = fs.createreadstream(pathname);

rs.on('data', function (chunk) );

rs.on('end', function () ) ;

**中data事件會源源不斷地被觸發,不管dosomething函式是否處理得過來。**可以繼續做如下改造,以解決這個問題。

複製****如下:

var rs = fs.createreadstream(src) ;

rs.on('data', function (chunk) ) ;

}) ;

rs.on('end', function () )  ;

給dosomething函式加上了**,因此我們可以在處理資料前暫停資料讀取,並在處理資料後繼續讀取資料。

此外,我們也可以為資料目標建立乙個只寫資料流,如下:

複製****如下:

var rs = fs.createreadstream(src) ;

var ws = fs.createwritestream(dst) ;

rs.on('data', function (chunk) ) ;

rs.on('end', function () ) ;

dosomething換成了往只寫資料流裡寫入資料後,以上**看起來就像是乙個檔案拷貝程式了。但是以上**存在上邊提到的問題,如果寫入速度跟不上讀取速度的話,只寫資料流內部的快取會爆倉。我們可以根據.write方法的返回值來判斷傳入的資料是寫入目標了,還是臨時放在了快取了,並根據drain事件來判斷什麼時候只寫資料流已經將快取中的資料寫入目標,可以傳入下乙個待寫資料了。因此**如下:

複製****如下:

var rs = fs.createreadstream(src) ;

var ws = fs.createwritestream(dst) ;

rs.on('data', function (chunk)

}) ;

rs.on('end', function () );

ws.on('drain', function () ) ;

最終實現了資料從唯讀資料流到只寫資料流的搬運,幷包括了防爆倉控制。因為這種使用場景很多,例如上邊的大檔案拷貝程式,nodejs直接提供了.pipe方法來做這件事情,其內部實現方式與上邊的**類似。

下面是乙個更加完整的複製檔案的過程:

複製****如下:

var fs = require('fs'),

path = require('path'),

out = process.stdout;

var filepath = '/bb/bigbear.mkv';

var readstream = fs.createreadstream(filepath);

var writestream = fs.createwritestream('file.mkv');

var stat = fs.statsync(filepath);

var totalsize = stat.size;

var passedlength = 0;

var lastsize = 0;

var starttime = date.now();

readstream.on('data', function(chunk)

});readstream.on('end', function() );

writestream.on('drain', function() );

settimeout(function show() else

}, 500);

可以把上面的**儲存為 "copy.js" 試驗一下我們新增了乙個遞迴的 settimeout (或者直接使用setinterval)來做乙個旁觀者,

每500ms觀察一次完成進度,並把已完成的大小、百分比和複製速度一併寫到控制台上,當複製完成時,計算總的耗費時間。

三,總結一下

(1),理解stream概念。

(2),熟練使用相關stream的api

(3),注意細節的把控,比如:大檔案的拷貝,採用的使用 「chunk data」 的形式進行分片處理。

(4),pipe的使用

(5),再次強調乙個概念:乙個tcp連線既是可讀流,又是可寫流,而http連線則不同,乙個http request物件是可讀流,而http response物件則是可寫流。

學習筆記 nodejs之Buffer

buffer.isbuffer val buffer.bytelength string buffer例項常用方法 demo 與string相互轉換 與json相互轉換 建立位元組長度為size的buffer物件 list為存放多個buffer物件的陣列。將多個buffer合併在一起,並返回乙個新的...

學習筆記之NodeJs基本操作

nodejs安裝見文章 windows下安裝node.js及less 執行js檔案 node js 呼叫http模組,並指定埠為3000,向客戶端輸出 hello world 向node控制項臺輸出http server is listening at port 3000 退出當前的監聽 ctrl ...

NodeJS學習筆記之Connect中介軟體應用例項

一,開篇分析 我也介紹過 connect 中介軟體的使用以及 mongodb 的用法,今天就結合這兩個中介軟體,寫個實際的例子,不斷完善和重構,已達到 充分學習的目的。好了,廢話不說了,直接進入主題。二,需求分析 1 使用者註冊,登入功能 沒有涉及很複雜的互動場景,註冊時會有使用者判斷是否已存在 2...