亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

如何用Node.js編寫內存效率高的應用程序

 更新時間:2021年04月30日 14:49:19   作者:淺笑·  
這篇文章主要介紹了如何用Node.js編寫內存效率高的應用程序,對Node.js感興趣的同學,可以參考下

前言

軟件應用程序在計算機的主存儲器中運行,我們稱之為隨機存取存儲器(RAM)。JavaScript,尤其是 Nodejs(服務端js)允許我們?yōu)榻K端用戶編寫從小型到大型的軟件項目。處理程序的內存總是一個棘手的問題,因為糟糕的實現(xiàn)可能會阻塞在給定服務器或系統(tǒng)上運行的所有其他應用程序。C 和 C++程序員確實關心內存管理,因為隱藏在代碼的每個角落都有可能出現(xiàn)可怕的內存泄漏。但是對于 JS 開發(fā)者來說,你真的有關心過這個問題嗎?

由于 JS 開發(fā)人員通常在專用的高容量服務器上進行 web 服務器編程,他們可能不會察覺多任務處理的延遲。比方說在開發(fā) web 服務器的情況下,我們也會運行多個應用程序,如數據庫服務器( MySQL )、緩存服務器( Redis )和其他需要的應用。我們需要知道它們也會消耗可用的主內存。如果我們隨意地編寫應用程序,很可能會降低其他進程的性能,甚至讓內存完全拒絕對它們的分配。在本文中,我們通過解決一個問題來了解 NodeJS 的流、緩沖區(qū)和管道等結構,并了解它們分別如何支持編寫內存有效的應用程序。

問題:大文件復制

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

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!');
});

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

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

$ 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 的數據到它的緩沖區(qū),導致了錯誤發(fā)生在讀取文件的過程中。為了解決這個問題,當你在進行 I/O 密集操作的時候(復制、處理、壓縮等),最好考慮一下內存的情況。

NodeJS 中的 Streams 和 Buffers

為了解決上述問題,我們需要一個辦法把大文件切成許多文件塊,同時需要一個數據結構去存放這些文件塊。一個 buffer 就是用來存儲二進制數據的結構。接下來,我們需要一個讀寫文件塊的方法,而 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>

如果我們已經有了一些數據,比如數組或者別的數據集,我們可以為它們創(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()之類的重要方法,能夠深入到其所存儲的數據當中去。

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

Streams(流)

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

流的類型總共有四種:

  • 可讀流(用于讀取數據)
  • 可寫流(用于寫入數據)
  • 雙工流(同時可用于讀寫)
  • 轉換流(一種用于處理數據的自定義雙工流,如壓縮,檢查數據等)

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

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

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

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

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

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

1.監(jiān)聽來自可讀流的數據塊

2.把數據塊寫進可寫流

3.跟蹤文件復制的進度

我們把這段代碼命名為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!");
    });
    
});

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

data:當一個數據塊被讀取時觸發(fā)

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

error:當讀取數據塊的時候出錯時觸發(fā)

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

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

然而,當我們通過任務管理器觀察程序在運行過程中的內存狀況時,依舊有一個問題。

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

發(fā)生了什么?

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

Disk Read: 53.4 MiB/s

Disk Write: 14.8 MiB/s

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

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

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

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

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

讓我們將這個程序命名為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!");
    });
    
});

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

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

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

運行一下。

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

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

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

Disk Read: 35.5 MiB/s

Disk Write: 35.5 MiB/s

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

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

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

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

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

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

  • 處理從卡夫卡到數據庫的數據流
  • 處理來自文件系統(tǒng)的數據流,動態(tài)壓縮并寫入磁盤
  • 更多……

結論

我寫這篇文章的動機,主要是為了說明即使 NodeJS 提供了很好的 API,我們也可能會一不留神就寫出性能很差的代碼。如果我們能更多地關注其內置的工具,我們便可以更好地優(yōu)化程序的運行方式。

以上就是如何用Node.js編寫內存效率高的應用程序的詳細內容,更多關于Node.js的資料請關注腳本之家其它相關文章!

相關文章

  • Node.js流管理利器Destroy包使用方法詳解

    Node.js流管理利器Destroy包使用方法詳解

    在 Node.js 中,流(Streams)是處理大量數據的高效方式,然而,流的不當處理可能會引發(fā)資源泄露,本文將詳解如何使用 destroy 包,確保流被正確銷毀,需要的朋友可以參考下
    2024-05-05
  • 簡單了解node npm cnpm的具體使用方法

    簡單了解node npm cnpm的具體使用方法

    這篇文章主要介紹了簡單了解node npm cnpm的具體使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-02-02
  • 詳解nodejs微信公眾號開發(fā)——2.自動回復

    詳解nodejs微信公眾號開發(fā)——2.自動回復

    這篇文章主要介紹了詳解nodejs微信公眾號開發(fā)——2.自動回復,非常具有實用價值,需要的朋友可以參考下
    2017-04-04
  • npm?install安裝報錯:gyp?info?it?worked?if?it?ends?with?ok的解決方法

    npm?install安裝報錯:gyp?info?it?worked?if?it?ends?with?

    今天新啟動一個項目,在 npm install 安裝依賴項時出現(xiàn)報錯,所以下面這篇文章主要給大家介紹了關于npm?install安裝報錯:gyp?info?it?worked?if?it?ends?with?ok的解決方法,需要的朋友可以參考下
    2022-07-07
  • 淺談Express異步進化史

    淺談Express異步進化史

    本篇文章主要介紹了淺談Express異步進化史 ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • 基于node.js實現(xiàn)微信支付退款功能

    基于node.js實現(xiàn)微信支付退款功能

    在微信開發(fā)中有有付款就會有退款,這樣的功能非常常見,這篇文章主要介紹了node.js實現(xiàn)微信支付退款功能,需要的朋友可以參考下
    2017-12-12
  • 輕松創(chuàng)建nodejs服務器(3):代碼模塊化

    輕松創(chuàng)建nodejs服務器(3):代碼模塊化

    這篇文章主要介紹了輕松創(chuàng)建nodejs服務器(3):代碼模塊化,本文是對第一節(jié)的例子作了封裝,需要的朋友可以參考下
    2014-12-12
  • NodeJs+MySQL實現(xiàn)注冊登錄功能

    NodeJs+MySQL實現(xiàn)注冊登錄功能

    這篇文章主要為大家詳細介紹了NodeJs+MySQL實現(xiàn)注冊登錄功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • 手把手教你更優(yōu)雅的修改node_modules里的代碼

    手把手教你更優(yōu)雅的修改node_modules里的代碼

    這篇文章主要給大家介紹了關于如何更優(yōu)雅的修改node_modules里的代碼的相關資料,文中通過實例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2023-02-02
  • nodejs制作小爬蟲功能示例

    nodejs制作小爬蟲功能示例

    這篇文章主要介紹了nodejs制作小爬蟲功能,結合實例形式分析了node.js安裝request、cheerio模塊及請求發(fā)送、數據庫操作等相關實現(xiàn)技巧,需要的朋友可以參考下
    2020-02-02

最新評論