如何用Node.js編寫內(nèi)存效率高的應(yīng)用程序

本篇內(nèi)容介紹了“如何用Node.js編寫內(nèi)存效率高的應(yīng)用程序”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、成都微信小程序、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了安仁免費建站歡迎大家使用!

問題:大文件復(fù)制

如果有人被要求用 NodeJS 寫一段文件復(fù)制的程序,那么他會迅速寫出下面這段代碼:

const fs = require('fs');  let fileName = process.argv[2];  let destPath = process.argv[3];  fs.readFile(fileName, (err, data) => {      if (err) throw err;      fs.writeFile(destPath || 'output', data, (err) => {          if (err) throw err;      });       console.log('New file has been created!');  });

這段代碼簡單地根據(jù)輸入的文件名和路徑,在嘗試對文件讀取后把它寫入目標(biāo)路徑,這對于小文件來說是不成問題的。

現(xiàn)在假設(shè)我們有一個大文件(大于4 GB)需要用這段程序來進行備份。就以我的一個達 7.4G 的超高清4K 電影為例子好了,我用上述的程序代碼把它從當(dāng)前目錄復(fù)制到別的目錄。

$ node basic_copy.js cartoonMovie.mkv ~/Documents/bigMovie.mkv

然后在 Ubuntu(Linux )系統(tǒng)下我得到了這段報錯:

/home/shobarani/Workspace/basic_copy.js:7      if (err) throw err;               ^  RangeError: File size is greater than possible Buffer: 0x7fffffff bytes      at FSReqWrap.readFileAfterStat [as oncomplete] (fs.js:453:11)

正如你看到的那樣,由于 NodeJS 最大只允許寫入 2GB 的數(shù)據(jù)到它的緩沖區(qū),導(dǎo)致了錯誤發(fā)生在讀取文件的過程中。為了解決這個問題,當(dāng)你在進行 I/O 密集操作的時候(復(fù)制、處理、壓縮等),最好考慮一下內(nèi)存的情況。

NodeJS 中的 Streams 和 Buffers

為了解決上述問題,我們需要一個辦法把大文件切成許多文件塊,同時需要一個數(shù)據(jù)結(jié)構(gòu)去存放這些文件塊。一個 buffer 就是用來存儲二進制數(shù)據(jù)的結(jié)構(gòu)。接下來,我們需要一個讀寫文件塊的方法,而 Streams 則提供了這部分能力。

Buffers(緩沖區(qū))

我們能夠利用 Buffer 對象輕松地創(chuàng)建一個 buffer。

let buffer = new Buffer(10); # 10 為 buffer 的體積  console.log(buffer); # prints <Buffer 00 00 00 00 00 00 00 00 00 00>

在新版本的 NodeJS (>8)中,你也可以這樣寫。

let buffer = new Buffer.alloc(10);  console.log(buffer); # prints <Buffer 00 00 00 00 00 00 00 00 00 00>

如果我們已經(jīng)有了一些數(shù)據(jù),比如數(shù)組或者別的數(shù)據(jù)集,我們可以為它們創(chuàng)建一個 buffer。

let name = 'Node JS DEV';  let buffer = Buffer.from(name);  console.log(buffer) # prints <Buffer 4e 6f 64 65 20 4a 53 20 44 45 5>

Buffers 有一些如 buffer.toString() 和 buffer.toJSON() 之類的重要方法,能夠深入到其所存儲的數(shù)據(jù)當(dāng)中去。

我們不會為了優(yōu)化代碼而去直接創(chuàng)建原始 buffer。NodeJS 和 V8 引擎在處理 streams 和網(wǎng)絡(luò) socket 的時候就已經(jīng)在創(chuàng)建內(nèi)部緩沖區(qū)(隊列)中實現(xiàn)了這一點。

Streams(流)

簡單來說,流就像 NodeJS 對象上的任意門。在計算機網(wǎng)絡(luò)中,入口是一個輸入動作,出口是一個輸出動作。我們接下來將繼續(xù)使用這些術(shù)語。

流的類型總共有四種:

  •  可讀流(用于讀取數(shù)據(jù))

  •  可寫流(用于寫入數(shù)據(jù))

  •  雙工流(同時可用于讀寫)

  •  轉(zhuǎn)換流(一種用于處理數(shù)據(jù)的自定義雙工流,如壓縮,檢查數(shù)據(jù)等)

下面這句話可以清晰地闡述為什么我們應(yīng)該使用流。

Stream API (尤其是 stream.pipe() 方法)的一個重要目標(biāo)是將數(shù)據(jù)緩沖限制在可接受的水平,這樣不同速度的源和目標(biāo)就不會阻塞可用內(nèi)存。

我們需要一些辦法去完成任務(wù)而不至于壓垮系統(tǒng)。這也是我們在文章開頭就已經(jīng)提到過的。

如何用Node.js編寫內(nèi)存效率高的應(yīng)用程序

上面的示意圖中我們有兩個類型的流,分別是可讀流和可寫流。.pipe() 方法是一個非?;镜姆椒?,用于連接可讀流和可寫流。如果你不明白上面的示意圖,也沒關(guān)系,在看完我們的例子以后,你可以回到示意圖這里來,那個時候一切都會顯得理所當(dāng)然。管道是一種引人注目的機制,下面我們用兩個例子來說明它。

解法1(簡單地使用流來復(fù)制文件)

讓我們設(shè)計一種解法來解決前文中大文件復(fù)制的問題。首先我們要創(chuàng)建兩個流,然后執(zhí)行接下來的幾個步驟。

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.  監(jiān)聽來自可讀流的數(shù)據(jù)塊

  3.  把數(shù)據(jù)塊寫進可寫流

  4.  跟蹤文件復(fù)制的進度

我們把這段代碼命名為 streams_copy_basic.js

/*      A file copy with streams and events - Author: Naren Arya  */  const stream = require('stream');  const fs = require('fs');  let fileName = process.argv[2];  let destPath = process.argv[3];  const readabale = fs.createReadStream(fileName);  const writeable = fs.createWriteStream(destPath || "output");  fs.stat(fileName, (err, stats) => {      this.fileSize = stats.size;      this.counter = 1;      this.fileArray = fileName.split('.');         try {          this.duplicate = destPath + "/" + this.fileArray[0] + '_Copy.' + this.fileArray[1];      } catch(e) {          console.exception('File name is invalid! please pass the proper one');      }        process.stdout.write(`File: ${this.duplicate} is being created:`);        readabale.on('data', (chunk)=> {          let percentageCopied = ((chunk.length * this.counter) / this.fileSize) * 100;          process.stdout.clearLine();  // clear current text          process.stdout.cursorTo(0);          process.stdout.write(`${Math.round(percentageCopied)}%`);          writeable.write(chunk);          this.counter += 1;      });        readabale.on('end', (e) => {          process.stdout.clearLine();  // clear current text          process.stdout.cursorTo(0);          process.stdout.write("Successfully finished the operation");          return;      });        readabale.on('error', (e) => {          console.log("Some error occured: ", e);      });         writeable.on('finish', () => {         console.log("Successfully created the file copy!");      });     });

在這段程序中,我們接收用戶傳入的兩個文件路徑(源文件和目標(biāo)文件),然后創(chuàng)建了兩個流,用于把數(shù)據(jù)塊從可讀流運到可寫流。然后我們定義了一些變量去追蹤文件復(fù)制的進度,然后輸出到控制臺(此處為 console)。與此同時我們還訂閱了一些事件:

data:當(dāng)一個數(shù)據(jù)塊被讀取時觸發(fā)

end:當(dāng)一個數(shù)據(jù)塊被可讀流所讀取完的時候觸發(fā)

error:當(dāng)讀取數(shù)據(jù)塊的時候出錯時觸發(fā)

運行這段程序,我們可以成功地完成一個大文件(此處為7.4 G)的復(fù)制任務(wù)。

$ time node streams_copy_basic.js cartoonMovie.mkv ~/Documents/4kdemo.mkv

然而,當(dāng)我們通過任務(wù)管理器觀察程序在運行過程中的內(nèi)存狀況時,依舊有一個問題。

如何用Node.js編寫內(nèi)存效率高的應(yīng)用程序

4.6GB?我們的程序在運行時所消耗的內(nèi)存,在這里是講不通的,以及它很有可能會卡死其他的應(yīng)用程序。

發(fā)生了什么?

如果你有仔細(xì)觀察上圖中的讀寫率,你會發(fā)現(xiàn)一些端倪。

Disk Read: 53.4 MiB/s

Disk Write: 14.8 MiB/s

這意味著生產(chǎn)者正在以更快的速度生產(chǎn),而消費者無法跟上這個速度。計算機為了保存讀取的數(shù)據(jù)塊,將多余的數(shù)據(jù)存儲到機器的RAM中。這就是RAM出現(xiàn)峰值的原因。

上述代碼在我的機器上運行了3分16秒&hellip;&hellip;

17.16s user 25.06s system 21% cpu 3:16.61 total

解法2(基于流和自動背壓的文件復(fù)制)

為了克服上述問題,我們可以修改程序來自動調(diào)整磁盤的讀寫速度。這個機制就是背壓。我們不需要做太多,只需將可讀流導(dǎo)入可寫流即可,NodeJS 會負(fù)責(zé)背壓的工作。

讓我們將這個程序命名為 streams_copy_efficient.js

/*      A file copy with streams and piping - Author: Naren Arya  */  const stream = require('stream');  const fs = require('fs');  let fileName = process.argv[2];  let destPath = process.argv[3];  const readabale = fs.createReadStream(fileName);  const writeable = fs.createWriteStream(destPath || "output");  fs.stat(fileName, (err, stats) => {      this.fileSize = stats.size;      this.counter = 1;      this.fileArray = fileName.split('.');       try {          this.duplicate = destPath + "/" + this.fileArray[0] + '_Copy.' + this.fileArray[1];      } catch(e) {          console.exception('File name is invalid! please pass the proper one');      }         process.stdout.write(`File: ${this.duplicate} is being created:`);       readabale.on('data', (chunk) => {          let percentageCopied = ((chunk.length * this.counter) / this.fileSize) * 100;          process.stdout.clearLine();  // clear current text          process.stdout.cursorTo(0);          process.stdout.write(`${Math.round(percentageCopied)}%`);          this.counter += 1;      });         readabale.pipe(writeable); // Auto pilot ON!         // In case if we have an interruption while copying      writeable.on('unpipe', (e) => {          process.stdout.write("Copy has failed!");      });     });

在這個例子中,我們用一句代碼替換了之前的數(shù)據(jù)塊寫入操作。

readabale.pipe(writeable); // Auto pilot ON!

這里的 pipe 就是所有魔法發(fā)生的原因。它控制了磁盤讀寫的速度以至于不會阻塞內(nèi)存(RAM)。

運行一下。

$ time node streams_copy_efficient.js cartoonMovie.mkv ~/Documents/4kdemo.mkv

我們復(fù)制了同一個大文件(7.4 GB),讓我們來看看內(nèi)存利用率。

如何用Node.js編寫內(nèi)存效率高的應(yīng)用程序

震驚!現(xiàn)在 Node 程序僅僅占用了61.9 MiB 的內(nèi)存。如果你觀察到讀寫速率的話:

Disk Read: 35.5 MiB/s

Disk Write: 35.5 MiB/s

在任意給定的時間內(nèi),因為背壓的存在,讀寫速率得以保持一致。更讓人驚喜的是,這段優(yōu)化后的程序代碼整整比之前的快了13秒。

12.13s user 28.50s system 22% cpu 3:03.35 total

由于 NodeJS 流和管道,內(nèi)存負(fù)載減少了98.68%,執(zhí)行時間也減少了。這就是為什么管道是一個強大的存在。

61.9 MiB 是由可讀流創(chuàng)建的緩沖區(qū)大小。我們還可以使用可讀流上的 read 方法為緩沖塊分配自定義大小。

const readabale = fs.createReadStream(fileName);  readable.read(no_of_bytes_size);

除了本地文件的復(fù)制以外,這個技術(shù)還可以用于優(yōu)化許多 I/O 操作的問題:

  •  處理從卡夫卡到數(shù)據(jù)庫的數(shù)據(jù)流

  •  處理來自文件系統(tǒng)的數(shù)據(jù)流,動態(tài)壓縮并寫入磁盤

  •  更多&hellip;&hellip;

“如何用Node.js編寫內(nèi)存效率高的應(yīng)用程序”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

本文題目:如何用Node.js編寫內(nèi)存效率高的應(yīng)用程序
標(biāo)題URL:http://muchs.cn/article32/pgdssc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制開發(fā)動態(tài)網(wǎng)站、ChatGPT、網(wǎng)頁設(shè)計公司、網(wǎng)站排名Google

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站