Nodejs Buffer的使用及Stream流和事件機制詳解
前言
昨天我們講述了 Buffer類
的基礎(chǔ)用法,今天我們介紹一下 Buffer類 的一些應(yīng)用以及 流(Stream)
的概念和用法。
Buffer 使用
Buffer 拼接
Buffer 在使用時,通常是以一段一段的方式傳輸。以下是一段經(jīng)典的從輸入流中讀取內(nèi)容的代碼:
const fs = require("fs"); // const readFs = fs.createReadStream("./readExam.md", { // highWaterMark: 1 // }); const readFs = fs.createReadStream("./readExam.md"); let data = ""; readFs.on("data", (chunk) => { data += chunk; }); readFs.on("end", () => { console.log("buffer value: ", data); });
?? data事件中獲取的 chunk對象
是 Buffer對象
或 String對象
,然后與 data變量
拼接成目標(biāo) Buffer對象。
上述的代碼中我們構(gòu)造了一個可讀流。值得一提的是,可讀流有一個設(shè)置編碼的方法:
readable.setEncoding(encoding);
該方法能指定 data事件 中傳遞的元素的編碼類型,避免發(fā)生一些特殊的錯誤:
const readFs = fs.createReadStream("./readExam.md"); readFs.setEncoding('utf-8');
編碼問題
在不設(shè)置 highWaterMark
屬性的情況下,你無需顯示地去調(diào)用 setEncoding
方法,data事件默認(rèn)就能接受字符串或者 Buffer 對象兩種參數(shù)。但你仍需注意,目前僅支持 UTF8
和 UTF16LE
兩種編碼的字符串,所以如果讀取的目標(biāo)文件是其他編碼的,打印結(jié)果將會是亂碼!
?? 假設(shè)每讀取一個Buffer就會觸發(fā)一次data事件,那么無論如何設(shè)置編碼,觸發(fā)data事件的次數(shù)依舊相同。也就是說,如果你讀的文件中內(nèi)容是漢字,要觸發(fā)三次data事件才會進行一次拼接。因此在這種情況下中文會出現(xiàn)亂碼。
而在調(diào)用setEncoding()時,可讀流對象在內(nèi)部設(shè)置了一個decoder對象。每次data事件都通過該decoder對象進行Buffer到字符串的解碼,然后傳遞給調(diào)用者。而decoder內(nèi)部是會對是否為寬字節(jié)進行判斷,從而進行轉(zhuǎn)碼。
拼接的正確姿勢
正確的拼接方式是用一個數(shù)組來存儲接收到的所有Buffer片段并記錄下所有片段的總長度,然后調(diào)用Buffer.concat()
方法生成一個合并的Buffer對象。
const fs = require("fs"); const readFs = fs.createReadStream("./readExam.md"); let chunks = []; let size = 0; readFs.on("data", (chunk) => { const chunkBuf = new Buffer.from(chunk); chunks.push(chunkBuf); size += chunkBuf.length; }); readFs.on("end", () => { const buf = Buffer.concat(chunks, size); const str = buf.toString(); // 對應(yīng)編碼方式,如果不支持則需要引入外部庫 })
文件讀取
?? Nodejs 提供了一個通過 Buffer 讀取文件的方法 fs.readFile()
,可以簡化讀取文件的操作。同時該方法還有 Sync
模式,及它的同步方法,返回一個Buffer對象。
但是注意,由于V8的內(nèi)存限制,你無法通過 fs.readFile()
和 fs.writeFile()
直接對大文件進行字符串操作,而需改用 fs.createReadStream()
和 fs.createWriteStream()
方法通過流的方式實現(xiàn)對大文件的操作。具體請參考接下來的 Stream 的介紹。
而如果不需要進行字符串層面的操作,則不需要借助V8來處理,只進行純粹的Buffer操作,這不會受到V8堆內(nèi)存的限制,只會受到電腦物理內(nèi)存的限制。
性能
Buffer 的使用除了與字符串的轉(zhuǎn)換有性能損耗外,在文件的讀取時,有一個highWaterMark
設(shè)置對性能的影響至關(guān)重要。其默認(rèn)值為64KB。
fs.createReadStream()
的工作方式是在內(nèi)存中準(zhǔn)備一段Buffer內(nèi)存,然后在fs.read()讀取時逐步從磁盤中將字節(jié)復(fù)制到Buffer內(nèi)存中。完成一次讀取時,則從這個Buffer中通過slice()方法取出部分?jǐn)?shù)據(jù)作為一個小Buffer對象,再通過data事件傳遞給調(diào)用方。如果Buffer用完,則重新分配一個;如果還有剩余,則繼續(xù)使用。而每次讀取的長度就是戶指定的 highWaterMark
,在合理范圍內(nèi),該值越大,讀取速度越快。
fs.createReadStream(path, [options])
?? 最開始我們將 highWaterMark
設(shè)置為 1 ,然后讀取中文出現(xiàn)亂碼也是這個原因
在網(wǎng)絡(luò)中的應(yīng)用
在Web應(yīng)用中,字符串轉(zhuǎn)換到Buffer是時時刻刻發(fā)生的,提高字符串到Buffer的轉(zhuǎn)換效率,可以很大程度地提高網(wǎng)絡(luò)吞吐率。因此,Nodejs內(nèi)部會通過預(yù)先轉(zhuǎn)換靜態(tài)內(nèi)容為Buffer對象緩存著,以減少CPU的重復(fù)使用,節(jié)省服務(wù)器資源。
const http = require('http'); const HOST = "127.0.0.1"; const PORT = 6869; const server = http.createServer(); server.listen({ port: PORT, host: HOST }, () => { console.log(`server listen on `, server.address()); }); let resData = ""; for (let i = 0; i < 1024*10; i++) { resData += "a"; } // resData = new Buffer.from(resData); // 監(jiān)聽客戶端發(fā)起的 request server.on('request', (req, res) => { console.log('connect success!\n'); res.writeHead(200); res.end(resData); }) server.on('clientError', (err, socket) => { socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); });
?? 你無需顯示地調(diào)用18行代碼。
流 Stream
Nodejs 中原生內(nèi)置的 stream模塊
用于處理流式數(shù)據(jù),許多核心模塊都在其內(nèi)部實現(xiàn)了流操作。流還適用于網(wǎng)絡(luò)傳輸、JSON解析器、RFC(遠(yuǎn)程調(diào)用)等。Stream
繼承自 EventEmitter
,具備基本的自定義事件功能,同時抽象出標(biāo)準(zhǔn)的事件和方法它擁有四個抽象類:
- Readable:可讀流,讀取底層的I/O數(shù)據(jù)源。
- Writeable:可寫流,將數(shù)據(jù)寫入到目標(biāo)中。
- Duplex:雙工流,即可讀也可寫。
- Transform:轉(zhuǎn)換流,會修改數(shù)據(jù)的雙工流。
管道 pipe()
在可讀流中,有一個管道方法:pipe(),它的作用是關(guān)聯(lián)可讀流與可寫流,讓數(shù)據(jù)通過管道從可讀流進入到可寫流中。pipe()方法能接收一個Writable對象,并返回對目標(biāo)流的引用,從而可形成鏈?zhǔn)秸{(diào)用。
你可以用這個方法改寫之前的案例:
const fs = require('fs'); const readable = fs.createReadStream('./origin.txt'); const writable = fs.createWriteStream('./target.txt'); readable.pipe(writable); const fs = require("fs"); const readFs = fs.createReadStream("./readExam.md"); const writeFs = fs.createWriteStream("./outExam.md"); // 1.writ+end readFs.on("data", (chunk) => { // writeFs.write(chunk); }); readFs.on("end", () => { // writeFs.end(); }) // 2.pipe readFs.pipe(writeFs);
?? 之前我們提到的內(nèi)存限制,是因為V8本身是有內(nèi)存限制的,而通過
EventEmitter
Nodejs 的事件模塊目前只包含一個 EventEmitter類
(即事件觸發(fā)器),所有能觸發(fā)事件的對象都是 EventEmitter類 的實例。EventEmitter 通常被用作基類,在 Nodejs 內(nèi)部,凡是提供事件機制的模塊都會繼承它。
聲明了一個EventEmitter實例,on()方法用于注冊監(jiān)聽器,emit()方法用于觸發(fā)事件。在調(diào)用emit()方法時,傳遞了自定義的type參數(shù)。
const EventEmitter = require('events'); class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); myEmitter.on('click', (type) => { console.log(`觸發(fā)${type}事件`); }); myEmitter.emit('click', "點擊");
?? 可注冊多個相同名稱的事件,監(jiān)聽器會按照添加順序依次調(diào)用。事件模塊還提供了很多其它方法,例如 off()
用于解除事件綁定,once()
可以只監(jiān)聽一次事件。
總結(jié)
本節(jié)介紹了 Nodejs 中 Buffer對象 的一些具體使用方法和說明,并借此提及 Stream 的相關(guān)內(nèi)容,之后我將介紹一下 Nodejs 提供的標(biāo)準(zhǔn) I/O 方法,更多關(guān)于Nodejs Buffer Stream流的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
用nodeJS搭建本地文件服務(wù)器的幾種方法小結(jié)
本篇文章主要介紹了用nodeJS搭建本地文件服務(wù)器的幾種方法小結(jié),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03如何在 Node.js 中使用 axios 配置代理并實現(xiàn)圖片并發(fā)下載
這篇文章主要介紹了如何在Node.js中使用axios配置代理并實現(xiàn)圖片并發(fā)下載,本文通過實例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-07-07npm?ERR!?Node.js?v20.11.0錯誤的解決
在使用?npm?進行包管理和構(gòu)建項目的過程中,有時會遇到錯誤信息?npm?ERR!?Node.js?v20.11.0,本文就來介紹一下如何解決,感興趣的可以了解一下2024-02-02node.js中的fs.readlinkSync方法使用說明
這篇文章主要介紹了node.js中的fs.readlinkSync方法使用說明,本文介紹了fs.readlinkSync方法說明、語法、接收參數(shù)、使用實例和實現(xiàn)源碼,需要的朋友可以參考下2014-12-12