流是基於事件的API,用於管理和處理資料,而且有不錯的效率. 藉助事件和非阻塞I/O庫,流模組允許在其可用的時候動態處理,在其不需要的時候釋放掉.
流的概念
- 流是一組有序的,有起點和終點的位元組資料傳輸手段。
- 它不關心檔案的整體內容,只關注是否從檔案中讀到了資料,以及讀到資料之後的處理。
- 流是一個抽象介面,被 Node 中的很多物件所實現。比如HTTP 伺服器request和response物件都是流。
那麼說了這些,到底有哪些我們常用的流呢?下面我就一一介紹:
createReadStream
我們首先看的是可讀流,它的實現機制是在預設情況下不會將內容輸出,而是在內部先建立一個buffer,先讀取highWaterMark長度。可讀流有5個事件,分別是`data`,`close`,`open`,`end`,`error`,可讀流會瘋狂的觸發data事件,直到讀完為止,除此之外,createReadStream還有兩個事件,暫停(pause)和繼續(resume),讓我們來看一下用法:
複製程式碼
let fs = require('fs');
let path = require('path');
let re = fs.createReadStream(path.join(__dirname,'1.txt'),{
flags:'r', // 檔案的操作是讀取操作
encoding:'utf8', // 預設是null null代表的是buffer
autoClose:true, // 讀取完畢後自動關閉
highWaterMark:3,// 預設是64k 64*1024b start:0,//start和end是包前又包後,共10個位元組
end:9
})
re.on('open',function(){
console.log('檔案開啟了')
})
re.on('close',function(){
console.log('關閉')
})
//這裡採用釋出訂閱模式,內部有一個newLisenter函式來監聽例項上綁了哪些事件。而內部的事件觸發採用的是emit()
re.on('error',function(err){
console.log(err)
})
re.on('data',function(data){//觸發data就會一直流
console.log(data)
re.pause()//暫停
})
setInterval(function(){
re.resume()//繼續
},3000)
re.on('end',function(){
console.log('end')
})
複製程式碼
通過這幾個方法一實驗就知道內部是怎麼實現的了,我這裡用vscode跑了一下:
複製程式碼
createWriteStream
可寫流相對來說用法就比較簡單了,只有write
,end
,兩個方法。需要注意的是寫入的資料必須是字串或者buffer。我們來看一下它的用法:
let fs = require('fs');
let ws = fs.createWriteStream('./1.txt',{
flags:'w',
mode:0o666,
autoClose:true,
highWaterMark:3, // 預設寫是16k
encoding:'utf8',
start:0
});
// 寫入的資料必須是字串或者buffer
// flag代表是否能繼續寫
// 表示符表示的並不是是否寫入 表示的是能否繼續寫,但是返回false 也不會丟失,就是會把內容放到記憶體中
let flag = ws.write(1+'','utf8',()=>{}); // 非同步的方法
console.log(flag);
flag = ws.write(1+'','utf8',()=>{}); // 非同步的方法
console.log(flag);
// flag = ws.write(1+'','utf8',()=>{}); // 非同步的方法
// console.log(flag);
//ws.end('ok'); // 當寫完後 就不能再繼續寫了
//ws.write('123'); //write after end
複製程式碼
ws還有一個方法就是drain,抽乾方法 當都寫入完後會觸發drain事件, 必須快取區滿了 ,滿了後被清空了才會出發drain
let flag = ws.write(1+'','utf8',()=>{}); // 非同步的方法
console.log(flag);
flag = ws.write(1+'','utf8',()=>{}); // 非同步的方法
console.log(flag);
flag = ws.write(1+'','utf8',()=>{}); // 非同步的方法
console.log(flag);
ws.on('drain',function(){
console.log('drain')
});
複製程式碼
這個drain以後可以讓可寫流配合可讀流寫一個pipe方法。
pipe
是什麼呢? 其實就是一個讀一點寫一點的方法,實現的是下面的這樣的事:
let fs = require('fs');
let path = require('path');
let rs = fs.createReadStream(path.join(__dirname,'./1.txt'),{
highWaterMark:3
})
let ws = fs.createWriteStream(path.join(__dirname,'./2.txt'),{
highWaterMark:1
})
rs.on('data',function(chunk){
let flags = ws.write(chunk);
if(!flags){
rs.pause()
}
})
ws.on('drain',function(){
console.log('幹了')
rs.resume()
})
複製程式碼
而pipe 的實現方法就簡單的讓人感動:
rs.pipe(ws);
複製程式碼
readable
readable也是一個很實用的可讀流的方法,它可以很智慧的的實現暫停流。 readable的實現原理是:
當我只要建立一個流 就會先把快取區 填滿,等待著你自己消費 如果當前快取區被清空後會再次觸發readable事件 當你消費小於 最高水位線時 會自動新增highWater這麼多資料
let fs = require('fs');
let path = require('path');
let rs = fs.createReadStream(path.join(__dirname,'1.txt'),{
highWaterMark:3
});
// rs.on('data'); 正常情況下我們會直接用data,但是這個方法會一直讀
rs.on('readable',function(){
let result = rs.read(1);
console.log(result); //當我們讀一個時,這個時候輸出的就是一個
result = rs.read(1);
console.log(result);//當我們讀兩個時,這個時候輸出的就是倆個
result = rs.read(1);
console.log(result);//當我們讀到總共快取區的個數時,這個時候就讀完就接著觸發readable事件,
});
複製程式碼
執行結果是
這個readable還有一個特別智慧的操作,當讀一個或沒讀到highWaterMark的值時,並且半天沒操作,程式會自動給你‘續杯‘。
let fs = require('fs');
let path = require('path');
let rs = fs.createReadStream(path.join(__dirname,'1.txt'),{
highWaterMark:3
});
rs.on('readable',function(){
let result = rs.read(1);
console.log(rs._readableState.length); // 快取區的個數
setTimeout(function(){
console.log(rs._readableState.length);
},1000)
});
複製程式碼
結果如下
我想讀5個 快取區只有3個 他會更改快取區的大小再去讀取
Duplex
雙工流,讀的時候要以this.push(null)結尾,寫的時候要以 callback();結尾。
let {Duplex} = require('stream');
// 雙工流 又能讀 又能寫,而且讀取可以沒關係(互不干擾);
let d = Duplex({
read(){
this.push('hello');
this.push(null)
},
write(chunk,encoding,callback){
console.log(chunk);
callback();
}
});
d.on('data',function(data){
console.log(data);
});
d.write('hello');
複製程式碼
Transform
轉換流,就是Duplex 他不需要實現read write。
let {Transform} = require('stream');
// 他的引數和可寫流一樣
let tranform1 = Transform({
transform(chunk,encoding,callback){
this.push(chunk.toString().toUpperCase()); // 將輸入的內容放入到可讀流中
callback();
}
});
let tranform2 = Transform({
transform(chunk,encoding,callback){
console.log(chunk.toString());
callback();
}
});
// 等待你的輸入
// rs.pipe(ws);
// 希望將輸入的內容轉化成大寫在輸出出來
process.stdin.pipe(tranform1).pipe(tranform2);
複製程式碼
這個寫法是為了讓輸入轉化成大寫輸出,process.stdin(標準輸入)可視為可讀流。 最後用node 執行一下這個檔案,輸入小寫字母就會轉化成大寫字母。
可讀流裡只能放buffer或者字串 物件流裡可以放物件
自定義一個流
let {Readable} = require('stream');
// 想實現什麼流 就繼承這個流
// Readable裡面有一個read()方法,預設掉_read()
// Readable中提供了一個push方法你呼叫push方法就會觸發data事件
let index = 9;
class MyRead extends Readable{
_read(){
// 可讀流什麼時候停止呢? 當push null的時候停止
if(index-->0)return this.push('123');
this.push(null);
}
}
let mr = new MyRead;
mr.on('data',function(data){
console.log(data);
});
複製程式碼