NodeJS 中Stream 的基本使用
在 NodeJS 中,我們對文件的操作需要依賴核心模塊 fs , fs 中有很基本 API 可以幫助我們讀寫占用內(nèi)存較小的文件,如果是大文件或內(nèi)存不確定也可以通過 open 、 read 、 write 、 close 等方法對文件進行操作,但是這樣操作文件每一個步驟都要關(guān)心,非常繁瑣, fs 中提供了可讀流和可寫流,讓我們通過流來操作文件,方便我們對文件的讀取和寫入。
可讀流
1、createReadStream 創(chuàng)建可讀流
createReadStream 方法有兩個參數(shù),第一個參數(shù)是讀取文件的路徑,第二個參數(shù)為 options 選項,其中有八個參數(shù):
r null null 0o666 true 64 * 1024
createReadStream 的返回值為 fs.ReadStream 對象,讀取文件的數(shù)據(jù)在不指定 encoding 時,默認為 Buffer。
let fs = require("fs");
// 創(chuàng)建可讀流,讀取 1.txt 文件
let rs = fs.creatReadStream("1.txt", {
start: 0,
end: 3,
highWaterMark: 2
});
在創(chuàng)建可讀流后默認是不會讀取文件內(nèi)容的,讀取文件時,可讀流有兩種狀態(tài),暫停狀態(tài)和流動狀態(tài)。
注意:本篇的可寫流為流動模式,流動模式中有暫停狀態(tài)和流動狀態(tài),而不是暫停模式,暫停模式是另一種可讀流 readable 。
2、流動狀態(tài)
流動狀態(tài)的意思是,一旦開始讀取文件,會按照 highWaterMark 的值一次一次讀取,直到讀完為止,就像一個打開的水龍頭,水不斷的流出,直到流干,需要通過監(jiān)聽 data 事件觸發(fā)。
假如現(xiàn)在 1.txt 文件中的內(nèi)容為 0~9 十個數(shù)字,我們現(xiàn)在創(chuàng)建可讀流并用流動狀態(tài)讀取。
let fs = require("fs");
let rs = fs.createReadStream("1.txt", {
start: 0,
end: 3,
highWaterMark: 2
});
// 讀取文件
rs.on("data", data => {
console.log(data);
});
// 監(jiān)聽讀取結(jié)束
rs.on("end", () => {
console.log("讀完了");
});
// <Buffer 30 31>
// <Buffer 32 33>
// 讀完了
在上面代碼中,返回的 rs 對象監(jiān)聽了兩個事件:
data:每次讀取 highWaterMark 個字節(jié),觸發(fā)一次 data 事件,直到讀取完成,回調(diào)的參數(shù)為每次讀取的 Buffer;
end:當讀取完成時觸發(fā)并執(zhí)行回調(diào)函數(shù)。
我們希望最后讀到的結(jié)果是完整的,所以我們需要把每一次讀到的結(jié)果在 data 事件觸發(fā)時進行拼接,以前我們可能使用下面這種方式。
let fs = require("fs");
let rs = fs.createReadStream("1.txt", {
start: 0,
end: 3,
highWaterMark: 2
});
let str = "";
rs.on("data", data => {
str += data;
});
rs.on("end", () => {
console.log(str);
});
// 0123
在上面代碼中如果讀取的文件內(nèi)容是中文,每次讀取的 highWaterMark 為兩個字節(jié),不能組成一個完整的漢字,在每次讀取時進行 += 操作會默認調(diào)用 toString 方法,這樣會導致最后讀取的結(jié)果是亂碼。
在以后通過流操作文件時,大部分情況下都是在操作 Buffer,所以應該用下面這種方式來獲取最后讀取到的結(jié)果。
let fs = require("fs");
let rs = fs.createReadStream("1.txt", {
start: 0,
end: 3,
highWaterMark: 2
});
// 存儲每次讀取回來的 Buffer
let bufArr = [];
rs.on("data", data => {
bufArr.push(data);
});
rs.on("end", () => {
console.log(Buffer.concat(bufArr).toString());
});
// 0123
3、暫停狀態(tài)
在流動狀態(tài)中,一旦開始讀取文件,會不斷的觸發(fā) data 事件,直到讀完,暫停狀態(tài)是我們每讀取一次就直接暫停,不再繼續(xù)讀取,即不再觸發(fā) data 事件,除非我們主動控制繼續(xù)讀取,就像水龍頭打開放水一次后馬上關(guān)上水龍頭,下次使用時再打開。
類似于開關(guān)水龍頭的動作,也就是暫停和恢復讀取的動作,在可讀流返回的 rs 對象上有兩個對應的方法, pause 和 resume 。
在下面的場景中我們把創(chuàng)建可讀流的結(jié)尾位置更改成 9 ,在每次讀兩個字節(jié)并暫停一秒后恢復讀取,直到讀完 0~9 十個數(shù)字。
let fs = require("fs");
let rs = fs.createReadStream("1.txt", {
start: 0,
end: 9,
hithWaterMark: 2
});
let bufArr = [];
rs.on("data", data => {
bufArr.push(data);
rs.pause(); // 暫停讀取
console.log("暫停", new Date());
setTimeout(() => {
rs.resume(); // 恢復讀取
}, 1000)
});
rs.on("end", () => {
console.log(Buffer.concat(bufArr).toString());
});
// 暫停 2018-07-03T23:52:52.436Z
// 暫停 2018-07-03T23:52:53.439Z
// 暫停 2018-07-03T23:52:54.440Z
// 暫停 2018-07-03T23:52:55.442Z
// 暫停 2018-07-03T23:52:56.443Z
// 0123456789
4、錯誤監(jiān)聽
在通過可讀流讀取文件時都是異步讀取,在異步讀取中如果遇到錯誤也可以通過異步監(jiān)聽到,可讀流返回值 rs 對象可以通過 error 事件來監(jiān)聽錯誤,在讀取文件出錯時觸發(fā)回調(diào)函數(shù),回調(diào)函數(shù)參數(shù)為 err ,即錯誤對象。
let fs = require("fs");
// 讀取一個不存在的文件
let rs = fs.createReadStream("xxx.js", {
highWarterMark: 2
});
let bufArr = [];
rs.on("data", data => {
bufArr.push(data);
});
rs.on("err", err => {
console.log(err);
});
rs.on("end", () => {
console.log(Buffer.concat(bufArr).toString());
});
// { Error: ENOENT: no such file or directory, open '......xxx.js' ......}
5、打開和關(guān)閉文件的監(jiān)聽
流的適用性非常廣,不只是文件讀寫,也可以用在 http 中數(shù)據(jù)的請求和響應上,但是在針對文件讀取返回的 rs 上有兩個專有的事件用來監(jiān)聽文件的打開與關(guān)閉。
open 事件用來監(jiān)聽文件的打開,回調(diào)函數(shù)在打開文件后執(zhí)行, close 事件用來監(jiān)聽文件的關(guān)閉,如果創(chuàng)建的可讀流的 autoClose 為 true ,在自動關(guān)閉文件時觸發(fā),回調(diào)函數(shù)在關(guān)閉文件后執(zhí)行。
let fs = require("fs");
let rs = fs.createReadStream("1.txt", {
start: 0,
end: 3,
highWaterMark: 2
});
rs.on("open", () => {
console.log("open");
});
rs.on("close", () => {
console.log("close");
});
// open
在上面代碼我們看出只要創(chuàng)建了可讀流就會打開文件觸發(fā) open 事件,因為默認為暫停狀態(tài),沒有對文件進行讀取,所以不會關(guān)閉文件,即不會觸發(fā) close 事件。
let fs = require("fs");
let rs = fs.createReadStream("1.txt", {
start: 0,
end: 3,
hithWaterMark: 2
});
rs.on("open", () => {
console.log("open");
});
rs.on("data", data => {
console.log(data);
});
rs.on("end", () => {
console.log("end");
});
rs.on("close", () => {
console.log("close");
});
// open
// <Buffer 30 31>
// <Buffer 32 33>
// end
// close
從上面例子執(zhí)行的打印結(jié)果可以看出只有開始讀取文件并讀完后,才會關(guān)閉文件并觸發(fā) close 事件, end 事件的觸發(fā)要早于 close 。
可寫流
1、createWriteStream 創(chuàng)建可寫流
createWriteStream 方法有兩個參數(shù),第一個參數(shù)是讀取文件的路徑,第二個參數(shù)為 options 選項,其中有七個參數(shù):
w
utf8
null
0o666
true
16 * 1024
createWriteStream 返回值為 fs.WriteStream 對象,第一次寫入時會真的寫入文件中,繼續(xù)寫入,會寫入到緩存中。
let fs = require("fs");
// 創(chuàng)建可寫流,寫入 2.txt 文件
let ws = fs.createWriteStream("2.txt", {
start: 0,
highWaterMark: 3
});
2、可寫流的 write 方法
在可寫流中將內(nèi)容寫入文件需要使用 ws 的 write 方法,參數(shù)為寫入的內(nèi)容,返回值是一個布爾值,代表 highWaterMark 的值是否足夠當前的寫入,如果足夠,返回 true ,否則返回 false ,換種說法就是寫入內(nèi)容的長度是否超出了 highWaterMark ,超出返回 false 。
let fs = require("fs");
let ws = fs.createWriteSteam("2.txt", {
start: 0,
highWaterMark: 3
});
let flag1 = ws.write("1");
console.log(flag1);
let flag2 = ws.write("2");
console.log(flag2);
let flag3 = ws.write("3");
console.log(flag3);
// true
// true
// false
寫入不存在的文件時會自動創(chuàng)建文件,如果 start 的值不是 0 ,在寫入不存在的文件時默認找不到寫入的位置。
3、可寫流的 drain 事件
drain 意為 “吸干”,當前寫入的內(nèi)容已經(jīng)大于等于了 highWaterMark ,會觸發(fā) drain 事件,當內(nèi)容全部從緩存寫入文件后,會執(zhí)行回調(diào)函數(shù)。
let fs = require("fs");
let ws = fs.createWriteStream("2.txt", {
start: 0,
highWaterMark: 3
});
let flag1 = ws.write("1");
console.log(flag1);
let flag2 = ws.write("2");
console.log(flag2);
let flag3 = ws.write("3");
console.log(flag3);
ws.on("drain", () => {
console.log("吸干");
});
// true
// true
// false
4、可寫流的 end 方法
end 方法傳入的參數(shù)為最后寫入的內(nèi)容, end 會將緩存未寫入的內(nèi)容清空寫入文件,并關(guān)閉文件。
let fs = require("fs");
let ws = fs.createWriteStream("2.txt", {
start: 0,
highWaterMark: 3
});
let flag1 = ws.write("1");
console.log(flag1);
let flag2 = ws.write("2");
console.log(flag2);
let flag3 = ws.write("3");
console.log(flag3);
ws.on("drain", () => {
console.log("吸干");
});
ws.end("寫完了");
// true
// true
// false
在調(diào)用 end 方法后,即使再次寫入的值超出了 highWaterMark 也不會再觸發(fā) drain 事件了,此時打開 2.txt 后發(fā)現(xiàn)文件中的內(nèi)容為 "123寫完了"。
let fs = require("fs");
let ws = fs.createWriteStream("2.txt", {
start: 0,
highWaterMark: 3
});
ws.write("1");
ws.end("寫完了");
ws.write("2");
// Error [ERR_STREAM_WRITE_AFTER_END]: write after end...
在調(diào)用 end 方法后,不可以再調(diào)用 write 方法寫入,否則會報一個很常見的錯誤 write after end ,文件原有內(nèi)容會被清空,而且不會被寫入新內(nèi)容。
可寫流與可讀流混合使用
可寫流和可讀流一般配合來使用,讀來的內(nèi)容如果超出了可寫流的 highWaterMark ,則調(diào)用可讀流的 pause 暫停讀取,等待內(nèi)存中的內(nèi)容寫入文件,未寫入的內(nèi)容小于 highWaterMark 時,調(diào)用可寫流的 resume 恢復讀取,創(chuàng)建可寫流返回值的 rs 上的 pipe 方法是專門用來連接可讀流和可寫流的,可以將一個文件讀來的內(nèi)容通過流寫到另一個文件中。
let fs = require("pipe");
// 創(chuàng)建可讀流和可寫流
let rs = fs.createReadStream("1.txt", {
highWaterMark: 3
});
let ws = fs.createWriteStream("2.txt", {
highWaterMark: 2
});
// 將 1.txt 的內(nèi)容通過流寫入 2.txt 中
rs.pipe(ws);
通過上面的這種類似于管道的方式,將一個流從一個文件輸送到了另一個文件中,而且會根據(jù)讀流和寫流的 highWaterMark 自由的控制寫入的 “節(jié)奏”,不用擔心內(nèi)存的消耗。
總結(jié)
以上所述是小編給大家介紹的NodeJS 中Stream 的基本使用,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
node.js支持多用戶web終端實現(xiàn)及安全方案
這篇文章主要介紹了node.js支持多用戶web終端實現(xiàn)方案以及web終端安全性保證的解決方法,一起學習參考下。2017-11-11
node.js 微信開發(fā)之定時獲取access_token
本文給大家分享的是在使用node.js做微信開發(fā)的過程中如何定時獲取access_token的方法,有需要的小伙伴可以參考下2020-02-02
Node.js環(huán)境下JavaScript實現(xiàn)單鏈表與雙鏈表結(jié)構(gòu)
Node環(huán)境下通過npm可以獲取list的幾個相關(guān)庫,但是我們這里注重于自己動手實現(xiàn),接下來就一起來看一下Node.js環(huán)境下JavaScript實現(xiàn)單鏈表與雙鏈表結(jié)構(gòu)2016-06-06
基于node+websocket+html實現(xiàn)騰訊課堂聊天室聊天功能
這篇文章主要介紹了基于node+websocket+html實現(xiàn)騰訊課堂聊天室聊天功能,本文通過截圖實例代碼給大家介紹的非常詳細,對大家的工作或?qū)W習具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03
在Node.js中使用Express實現(xiàn)視頻評論的列表展示和刪除功能
在現(xiàn)代Web應用中,視頻內(nèi)容和互動功能(如評論)的結(jié)合極大地增加了用戶的參與度,本文將通過一個具體的例子,展示如何在Node.js環(huán)境中使用Express框架來實現(xiàn)視頻評論的列表展示和刪除功能,需要的朋友可以參考下2024-04-04
詳解nodejs express下使用redis管理session
本篇文章主要介紹了詳解nodejs express下使用redis管理session ,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04

