nodejs的stream模塊

Stream 是Node.js中最重要的組件和模式之一,在構建較復雜的系統時,通常將其拆解為功能獨立的若干部分。這些部分的接口遵循一定的規范,通過某種方式相連,以共同完成較復雜的任務。nodejs的核心模塊,基本上都是stream的的實例,比如process.stdout、http.clientRequest

什么是流?

  • 流是一組有序的,有起點和終點的字節數據傳輸手段
  • 它不關心文件的整體內容,只關注是否從文件中讀到了數據,以及讀到數據之后的處理
  • 流是一個抽象接口,被 Node 中的很多對象所實現。比如HTTP 服務器request和response對象都是流。

簡單的理解,流就是將大塊的東西,分小塊依次處理。就像你需要從水龍頭上接一杯水,那么當你擰開水龍頭,水管就會一點點的源源不斷的流出來給你。


image.png

那么流這種方式在程序當中又有什么優勢呢?先看如下代碼:

let fs = require('fs');
fs.readFile('./1.txt', 'utf8', function(err, data){
    // 1.txt 已經讀取完成
    console.log(data);
    fs.writeFile('/2.txt', data); // 將內容寫入2.txt中
});

以上兩個方法是實現的功能是將1.txt文件讀取到內存當中,再將它寫入到2.txt文件中。但是如果文件過大就會出現問題了,內存容易爆掉。那么這里比較合適的方式應該是讀寫交替進行,也就是使用流的方式讀寫文件,這樣不管文件有多大,都不會一下子耗盡內存,可以安全的執行完。

如下:

let fs = require('fs');
let readStream = fs.createReadStream('./1.txt');
let writeStream = fs.createWriteStream('./2.txt');
readStream.on('data', function(chunk) { // 當有數據流出時,寫入數據,chunk的類型為Buffer
    writeStream.write(chunk);
});
readStream.on('end', function() { // 當沒有數據時,關閉數據流
    writeStream.end();
}); 

流的四種類型

在nodejs中,有四種stream類型:

  • Readable - 可讀的流,用來讀取數據 (例如 fs.createReadStream()).
  • Writable - 可寫的流,用來寫數據 (例如 fs.createWriteStream()).
  • Duplex - 可讀寫的流(雙工流),可讀+可寫 (例如 net.Socket).
  • Transform - 轉換流,在讀寫過程中可以修改和變換數據的 Duplex 流 (比如 zlib.createDeflate()(數據壓縮/解壓)).

1、可讀流(Readable streams)

nodejs中常見的可讀流有:fs.createReadStream()、http.IncomingRequest、process.stdin

可讀流createReadStream用法如下:
// 創建可讀流
let rs = fs.createReadStream(path,[options]);

// 設置編碼格式
rs.setEncoding('utf8');

// 監聽open事件,打開文件時觸發
rs.on('open', function () {
    console.log(err);
});

//流切換到流動模式,數據會被盡可能快的讀出
rs.on("data",function(data){
    console.log(data); //讀取到的數據
});

// 該事件會在讀完數據后被觸發
rs.on("end",function(data){
    console.log("數據已經讀取完畢");
});

//如果讀取文件出錯了,會觸發error事件
rs.on("error",function(err){
    console.log("something is wrong during processing");
})

//文件關閉觸發
rs.on('close', function () {
     console.log('文件關閉');
});

1、path讀取文件的路徑
2、options

  • flags打開文件要做的操作,默認為'r'
  • encoding默認為null
  • start開始讀取的索引位置
  • end結束讀取的索引位置(包括結束位置)
  • highWaterMark讀取緩存區默認的大小64kb
    如果指定utf8編碼highWaterMark要大于3個字節

2、可寫流(Writable streams)

可寫流createReadStream

實現了stream.Readable接口的對象,將對象數據讀取為流數據,當監聽data事件后,開始發射數據

 let  fs = require("fs");
// 創建一個可以寫入的流,寫入到文件 1.txt 中
let  ws= fs.createWriteStream('1.txt');
let  data = '寫入流數據';
 
// 使用 utf8 編碼寫入數據
ws.write(data,'UTF8');
 
// 表明接下來沒有數據要被寫入 Writable 通過傳入可選的 chunk 和 encoding 參數,可以在關閉流之前再寫入一段數據 如果傳入了可選的 callback 函數,它將作為 'finish' 事件的回調函數
ws.end("最后寫入的數據","utf8",function(){
   console.log(" 我是'finish' 事件的回調函數")
});
 
// 在調用了 stream.end() 方法,且緩沖區數據都已經傳給底層系統之后, 'finish' 事件將被觸發。
ws.on('finish', function() {
  console.log("寫入完成。");
});

// 寫入時發生錯誤觸發
ws.on('error', function(err){
  console.log(err.stack);
});
 
// 創建可寫流
let  ws = fs.createWriteStream(path,[options]);

1、path讀取文件的路徑
2、options

  • flags打開文件要做的操作,默認為'w'
  • encoding默認為utf8
  • highWaterMark寫入緩存區的默認大小16kb

管道流pipe用法
將數據的滯留量限制到一個可接受的水平,以使得不同速度的來源和目標不會淹沒可用內存。
linux經典的管道的概念,前者的輸出是后者的輸入
pipe是一種最簡單直接的方法連接兩個stream,內部實現了數據傳遞的整個過程,在開發的時候不需要關注內部數據的流動

用法:

var from = fs.createReadStream('./1.txt');
var to = fs.createWriteStream('./2.txt');
from.pipe(to); // 就是從1.txt中讀一點就往2.txt中寫一點

3、雙工流(Duplex streams)

Duplex實際上就是繼承了Readable和Writable。
有了雙工流,我們可以在同一個對象上同時實現可讀和可寫,就好像同時繼承這兩個接口。 重要的是雙工流的可讀性和可寫性操作完全獨立于彼此。這僅僅是將兩個特性組合成一個對象

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);

最常見的Duplex stream應該就是net.Socket實例了。

4、轉換流(Transform streams)

轉換流的輸出是從輸入中計算出來的,Transform stream是Duplex stream的特例。也就是說,Transform stream也同時可讀可寫,它可以用來修改或轉換數據。然它跟Duplex stream的區別在于,Transform stream的輸出與輸入是存在相關性的。你可以認為轉換流就是一個函數,這個函數的輸入是一個可寫流,輸出是一個可讀流。

對于轉換流,我們不必實現read或write的方法,我們只需要實現一個transform方法,將兩者結合起來。它有write方法的意思,我們也可以用它來push數據。

例如:希望將輸入的內容轉化成大寫在輸出出來

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);

常見的Transform stream包括zlib、crypto,這里有個簡單例子:文件的gzip壓縮。

let fs = require('fs');
let zlib = require('zlib');

let gzip = zlib.createGzip();

// 將1.txt文件的內容,打包壓縮成compress.txt.gz
let inFile = fs.createReadStream('./file/1.txt');
let outGz = fs.createWriteStream('./file/compress.txt.gz');

inFile .pipe(gzip).pipe(outGz);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容

  • stream 流是一個抽象接口,在 Node 里被不同的對象實現。例如 request to an HTTP se...
    明明三省閱讀 3,423評論 1 10
  • 流的基本概念及理解 流是一種數據傳輸手段,是有順序的,有起點和終點,比如你要把數據從一個地方傳到另外一個地方流非常...
    October_yang閱讀 7,708評論 3 9
  • 一、什么是Stream(流) 流(stream)在 Node.js 中是處理流數據的抽象接口(abstract i...
    Brolly閱讀 5,419評論 0 0
  • 流是Node中最重要的組件和模式之一。在社區里有一句格言說:讓一切事務流動起來。這已經足夠來描述在Node中流...
    宮若石閱讀 570評論 0 0
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,809評論 18 139