這篇文章將為大家詳細(xì)講解有關(guān)如何實(shí)現(xiàn)node可讀流之流動(dòng)模式,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
站在用戶的角度思考問(wèn)題,與客戶深入溝通,找到資陽(yáng)網(wǎng)站設(shè)計(jì)與資陽(yáng)網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:網(wǎng)站建設(shè)、網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、申請(qǐng)域名、雅安服務(wù)器托管、企業(yè)郵箱。業(yè)務(wù)覆蓋資陽(yáng)地區(qū)。node的可讀流基于事件
可讀流之流動(dòng)模式,這種流動(dòng)模式會(huì)有一個(gè)"開(kāi)關(guān)",每次當(dāng)"開(kāi)關(guān)"開(kāi)啟的時(shí)候,流動(dòng)模式起作用,如果將這個(gè)"開(kāi)關(guān)"設(shè)置成暫停的話,那么,這個(gè)可讀流將不會(huì)去讀取文件,直到將這個(gè)"開(kāi)關(guān)"重新置為流動(dòng)。
讀取文件流程
讀取文件內(nèi)容的流程,主要為:
打開(kāi)文件,打開(kāi)文件成功,將觸發(fā)open事件,如果打開(kāi)失敗,觸發(fā)error事件和close事件,將文件關(guān)閉。
開(kāi)始讀取文件中的內(nèi)容,監(jiān)聽(tīng)data事件,數(shù)據(jù)處于流動(dòng)狀態(tài),可通過(guò)修改開(kāi)關(guān)的狀態(tài)來(lái)暫停讀取。
每次讀取到的內(nèi)容放入緩存中,并通過(guò)data事件將數(shù)據(jù)發(fā)布出去。
當(dāng)文件中的內(nèi)容讀取完畢之后,將文件關(guān)閉。
這一系列動(dòng)作都是基于事件來(lái)進(jìn)行操作的,而node中的事件我們都知道是一種發(fā)布訂閱模式來(lái)實(shí)現(xiàn)的。
下面我們來(lái)看一看,node是如何使用可讀流來(lái)讀取文件中的內(nèi)容?
node 可讀流參數(shù)
首先我們通過(guò)fs模塊來(lái)創(chuàng)建一個(gè)可讀流,可讀流接受兩個(gè)參數(shù):
第一個(gè)參數(shù)是要讀取的文件地址,在這里指明你要讀取哪個(gè)文件。
第二個(gè)參數(shù)是可選項(xiàng),這個(gè)參數(shù)是一個(gè)對(duì)象,用來(lái)指定可讀流的一些具體的參數(shù)。
如下幾個(gè)參數(shù)我們來(lái)一一說(shuō)明:
highWaterMark:設(shè)置高水位線,這個(gè)參數(shù)主要用于在讀取文件時(shí),可讀流會(huì)將文件中的內(nèi)容讀取到緩存當(dāng)中,而這里我們需要?jiǎng)?chuàng)建一個(gè)buffer來(lái)緩存這些數(shù)據(jù),所以這個(gè)參數(shù)是用來(lái)設(shè)置buffer的大小,如果不對(duì)這個(gè)參數(shù)進(jìn)行設(shè)置的話,可讀流默認(rèn)的配置64k。
flags:這個(gè)參數(shù)主要用于設(shè)置文件的執(zhí)行模式,比如說(shuō)我們具體的操作適用于讀取文件還是寫入文件等這些操作。如果是寫入文件的話那我們,使用的是w。如果是讀取文件的話那這個(gè)操作符就應(yīng)該是r。
下面這張表格就說(shuō)明了不同的符號(hào)代表不同含義:
符號(hào) | 含義 |
---|---|
r | 讀文件,文件不存在報(bào)錯(cuò) |
r+ | 讀取并寫入,文件不存在報(bào)錯(cuò) |
rs | 同步讀取文件并忽略緩存 |
w | 寫入文件,不存在則創(chuàng)建,存在則清空 |
wx | 排它寫入文件 |
w+ | 讀取并寫入文件,不存在則創(chuàng)建,存在則清空 |
wx+ | 和w+類似,排他方式打開(kāi) |
a | 追加寫入 |
ax | 與a類似,排他方式寫入 |
a+ | 讀取并追加寫入,不存在則創(chuàng)建 |
ax+ | 作用與a+類似,但是以排他方式打開(kāi)文件 |
autoClose:這個(gè)參數(shù)主要用于,對(duì)文件的關(guān)閉的一些控制。如果文件再打開(kāi)的過(guò)程或者其他操作的過(guò)程中出現(xiàn)了錯(cuò)誤的情況下,需要將文件進(jìn)行關(guān)閉。那這個(gè)參數(shù)是設(shè)置文件是否自動(dòng)關(guān)閉的功能。
encoding:node中用buffer來(lái)讀取文件操作的東西二進(jìn)制數(shù)據(jù)。這些數(shù)據(jù)展現(xiàn)出來(lái)的話我們是一堆亂碼,所以需要,要我們對(duì)這個(gè)數(shù)據(jù)指定一個(gè)具體的編碼格式。然后將會(huì)對(duì)這些數(shù)據(jù)進(jìn)行編碼轉(zhuǎn)化,這樣轉(zhuǎn)化出來(lái)的數(shù)據(jù)就是我們能看懂的數(shù)據(jù)。
starts:這個(gè)參數(shù)主要用于指定從什么位置開(kāi)始讀取文件中的內(nèi)容,默認(rèn)的話是從零開(kāi)始。
ends:這個(gè)參數(shù)主要用于指定定具體要讀取文件多長(zhǎng)的數(shù)據(jù),這里需要說(shuō)明一下,這個(gè)參數(shù)是包括本身的位置,也就是所謂的包前和包后。
下面我們來(lái)看看可讀流具體例子:
let fs = require("fs"); let rs = fs.createReadStream("./a.js", { highWaterMark: 3, encoding: "utf8", autoClose: true, start: 0, end: 9 }); rs.on("open", () => {console.log("open");}); rs.on("close", () => {console.log("close");}); rs.on("data", data => { console.log(data); rs.pause();//暫停讀取 此時(shí)流動(dòng)模式為暫停模式 }); setInterval(() => { rs.resume();//重新設(shè)置為流動(dòng)模式,開(kāi)始讀取數(shù)據(jù) }, 1000); rs.on("end", () => { console.log("end"); }); rs.on("error", err => { console.log(err); });
手寫可讀流第一步
上面我們說(shuō)過(guò),node可讀流是基于node的核心模塊事件來(lái)完成的,所以在實(shí)現(xiàn)我們自己的可讀流時(shí)需要繼承events模塊,代碼如下:
let fs = require('fs'); let EventEmitter = require('events'); class ReadStream extends EventEmitter { }
繼承了EventEmitter類,我們就可以使用EventEmitter類中的各個(gè)方法,并且同樣是采用發(fā)布訂閱的模式了處理事件。
第二步:處理可讀流配置的參數(shù)
上面我們提到,node中創(chuàng)建可讀流時(shí)可以對(duì)這個(gè)流配置具體的參數(shù),比如
let rs = fs.createReadStream("./a.js", { highWaterMark: 3, encoding: "utf8", autoClose: true, start: 0, end: 9 });
那么對(duì)于這些參數(shù),我們自己實(shí)現(xiàn)的可讀流類也需要對(duì)這些參數(shù)進(jìn)行處理,那么這些參數(shù)該如何進(jìn)行處理呢?
constructor(path, options = {}) { super(); this.path = path; //指定要讀取的文件地址 this.highWaterMark = options.highWaterMark || 64 * 1024; this.autoClose = options.autoClose || true; //是否自動(dòng)關(guān)閉文件 this.start = options.start || 0; //從文件哪個(gè)位置開(kāi)始讀取 this.pos = this.start; // pos會(huì)隨著讀取的位置改變 this.end = options.end || null; // null表示沒(méi)傳遞 this.encoding = options.encoding || null;// buffer編碼 this.flags = options.flags || 'r'; this.flowing = null; // 模式開(kāi)關(guān) this.buffer = Buffer.alloc(this.highWaterMark);// 根據(jù)設(shè)置創(chuàng)建一個(gè)buffer存儲(chǔ)讀出來(lái)的數(shù) this.open(); }
通常配置的原則是以用戶配置的參數(shù)為準(zhǔn),如果用戶沒(méi)有對(duì)這個(gè)參數(shù)進(jìn)行設(shè)置的話,就采用默認(rèn)的配置。
實(shí)現(xiàn)可讀流第三步:打開(kāi)文件
這里原理是使用node模塊fs中的open方法。首先我們來(lái)回顧下fs.open()方法的使用。
fs.open(filename,flags,[mode],callback); //實(shí)例 fs.open('./1,txt','r',function(err,fd){});
這里需要說(shuō)明下,回調(diào)函數(shù)callback中有2個(gè)參數(shù):
第一個(gè)是error,node中異步回調(diào)都會(huì)返回的一個(gè)參數(shù),用來(lái)說(shuō)明具體的錯(cuò)誤信息
第二個(gè)參數(shù)是fd,是文件描述符,用來(lái)標(biāo)識(shí)文件,等價(jià)于open函數(shù)的第一個(gè)參數(shù)
好了,現(xiàn)在我們來(lái)看看我們自己的可讀流的open方法該如何實(shí)現(xiàn)吧:
open() { fs.open(this.path, this.flags, (err, fd) => { //fd標(biāo)識(shí)的就是當(dāng)前this.path這個(gè)文件,從3開(kāi)始(number類型) if (err) { if (this.autoClose) { // 如果需要自動(dòng)關(guān)閉則去關(guān)閉文件 this.destroy(); // 銷毀(關(guān)閉文件,觸發(fā)關(guān)閉事件) } this.emit('error', err); // 如果有錯(cuò)誤觸發(fā)error事件 return; } this.fd = fd; // 保存文件描述符 this.emit('open', this.fd); // 觸發(fā)文件的打開(kāi)的方法 }); }
從代碼上我們可以看出:
fs.open函數(shù)是異步函數(shù),也就是說(shuō)callback是異步執(zhí)行的,在成功打開(kāi)文件的情況下,fd這個(gè)屬性也是異步獲取到的,這點(diǎn)需要注意。
另外重要的一點(diǎn)是,如果在打開(kāi)文件發(fā)生錯(cuò)誤時(shí),則表明打開(kāi)文件失敗,那么此時(shí)就需要將文件關(guān)閉。
實(shí)現(xiàn)可讀流第四步:讀取文件內(nèi)容
上面我們?cè)敿?xì)說(shuō)過(guò),可讀流自身定義了一個(gè)"開(kāi)關(guān)",當(dāng)我們要讀取文件中的內(nèi)容的時(shí)候,我們需要將這個(gè)"開(kāi)關(guān)"打開(kāi),那么node可讀流本身是如何來(lái)打開(kāi)這個(gè)"開(kāi)關(guān)"的呢?
監(jiān)聽(tīng)data事件
node可讀流通過(guò)監(jiān)聽(tīng)data事件來(lái)實(shí)現(xiàn)這個(gè)"開(kāi)關(guān)"的開(kāi)啟:
rs.on("data", data => { console.log(data); });
當(dāng)用戶監(jiān)聽(tīng)data事件的時(shí)候,"開(kāi)關(guān)"開(kāi)啟,不停的從文件中讀取內(nèi)容。那么node是怎么監(jiān)聽(tīng)data事件的呢?
答案就是 事件模塊的newListener
這是因?yàn)閚ode可讀流是基于事件的,而事件中,服務(wù)器就可以通過(guò)newListener事件監(jiān)聽(tīng)到從用戶這邊過(guò)來(lái)的所有事件,每個(gè)事件都有對(duì)應(yīng)的類型,當(dāng)用戶監(jiān)聽(tīng)的是data事件的時(shí)候,我們就可以獲取到,然后就可以去讀取文件中的內(nèi)容了,那我們自己的可讀流該如何實(shí)現(xiàn)呢?
// 監(jiān)聽(tīng)newListener事件,看是否監(jiān)聽(tīng)了data事件,如果監(jiān)聽(tīng)了data事件的話,就開(kāi)始啟動(dòng)流動(dòng)模式,讀取文件中的內(nèi)容 this.on("newListener", type => { if (type === "data") { // 開(kāi)啟流動(dòng)模式,開(kāi)始讀取文件中的內(nèi)容 this.flowing = true; this.read(); } });
好了,知道了這個(gè)"開(kāi)關(guān)"是如何打開(kāi)的,那么這個(gè)時(shí)候就到了真正讀取文件中內(nèi)容的關(guān)鍵時(shí)候了,先上代碼先:
read() { // 第一次讀取文件的話,有可能文件是還沒(méi)有打開(kāi)的,此時(shí)this.fd可能還沒(méi)有值 if (typeof this.fd !== "number") { // 如果此時(shí)文件還是沒(méi)有打開(kāi)的話,就觸發(fā)一次open事件,這樣文件就真的打開(kāi)了,然后再讀取 return this.once("open", () => this.read()); } // 具體每次讀取多少個(gè)字符,需要進(jìn)行計(jì)算,因?yàn)樽詈笠淮巫x取倒的可能比highWaterMark小 let howMuchRead = this.end ? Math.min(this.end - this.pos + 1, this.highWaterMark) : this.highWaterMark; fs.read(this.fd, this.buffer, 0, howMuchRead, this.pos, (err, byteRead) => { // this.pos 是每次讀取文件讀取的位置,是一個(gè)偏移量,每次讀取會(huì)發(fā)生變化 this.pos += byteRead; // 將讀取到的內(nèi)容轉(zhuǎn)換成字符串串,然后通過(guò)data事件,將內(nèi)容發(fā)布出去 let srr = this.encoding ? this.buffer.slice(0, byteRead).toString(this.encoding) : this.buffer.slice(0, byteRead); // 將內(nèi)容通過(guò)data事件發(fā)布出去 this.emit("data", srr); // 當(dāng)讀取到到內(nèi)容長(zhǎng)度和設(shè)置的highWaterMark一致的話,并且還是流動(dòng)模式的話,就繼續(xù)讀取 if ((byteRead === this.highWaterMark) && this.flowing) { return this.read(); } // 沒(méi)有更多的內(nèi)容了,此時(shí)表示文件中的內(nèi)容已經(jīng)讀取完畢 if (byteRead < this.highWaterMark) { // 讀取完成,發(fā)布end方法,并關(guān)閉文件 this.emit("end"); this.destory(); } }); }
這里我們特別要注意的是:
文件是否已經(jīng)打開(kāi),是否獲取到fd,如果沒(méi)有打開(kāi)的話,則再次觸發(fā)open方法
分批次讀取文件內(nèi)容,每次讀取的內(nèi)容是變化的,所以位置和偏移量是要?jiǎng)討B(tài)計(jì)算的
控制讀取停止的條件。
實(shí)現(xiàn)可讀流第五步:關(guān)閉文件
好了,到現(xiàn)在,基礎(chǔ)的讀取工作已經(jīng)完成,那么就需要將文件關(guān)閉了,上面的open和read方法里面都調(diào)用了一個(gè)方法:destory,沒(méi)錯(cuò),這個(gè)就是關(guān)閉文件的方法,好了,那么我們來(lái)看看這個(gè)方法該如何實(shí)現(xiàn)吧
destory() { if (typeof this.fd !== "number") { // 發(fā)布close事件 return this.emit("close"); } // 將文件關(guān)閉,發(fā)布close事件 fs.close(this.fd, () => { this.emit("close"); }); }
當(dāng)然這塊的原理就是調(diào)用fs模塊的close方法啦。
實(shí)現(xiàn)可讀流第六步:暫停和恢復(fù)
既然都說(shuō)了,node可讀流有一個(gè)神奇的"開(kāi)關(guān)",就像大壩的閥門一樣,可以控制水的流動(dòng),同樣也可以控制水的暫停啦。當(dāng)然在node可讀流中的暫停是停止對(duì)文件的讀取,恢復(fù)就是將開(kāi)關(guān)打開(kāi),繼續(xù)讀取文件內(nèi)容,那么這兩個(gè)分別對(duì)應(yīng)的方法就是pause()和resume()方法。
那么我們自己的可讀流類里面該如何實(shí)現(xiàn)這兩個(gè)方法的功能呢?非常簡(jiǎn)單:
我們?cè)诙x類的私有屬性的時(shí)候,定義了這樣一個(gè)屬性flowing,當(dāng)它的值為true時(shí)表示開(kāi)關(guān)打開(kāi),反之關(guān)閉。
pause() { this.flowing = false;// 將流動(dòng)模式設(shè)置成暫停模式,不會(huì)讀取文件 } resume() { this.flowing = true;//將模式設(shè)置成流動(dòng)模式,可以讀取文件 this.read();// 重新開(kāi)始讀取文件 }
關(guān)于“如何實(shí)現(xiàn)node可讀流之流動(dòng)模式”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。