Node.js後端開發 - 基礎篇 #8 流和管道

語言: CN / TW / HK

文章目錄

流-簡單介紹

流讀取檔案-createReadStream

流寫入檔案-createWriteStream

管道

管道例項演示

讀取檔案加密壓縮打包

讀取檔案解壓解密輸出到終端


上一篇文章我們介紹了nodejs的建立和刪除檔案、目錄,詳見:Node.js後端開發 - 基礎篇 #7 建立和刪除檔案、目錄, 這篇文章我們將介紹nodejs的流和管道,什麼是流呢?下面我們開始舉例編碼!

流-簡單介紹

MacBook-Pro:hello-nodejs luminal$ ls app.js count.js readMe.txt temp

這裡有個目錄 hello-nodejs,我用一個命令ls,把這個目錄下的所有內容給輸出出來,

它裡面有檔案和目錄,那麼我們要對這個輸出的資訊進行處理的話,就要用到流和管道,

如果我要檢視裡面是不是包含"app"關鍵字,我可以輸入如下命令:ls | grep app

那麼在這種情況下 ls 它就輸出了一個流,它的輸出的流作為 grep app 這個命令的輸入,

也就是 ls 的輸出作為 grep app 的輸入。然後我們還可以這麼做體驗一下 ls | grep app | grep js

MacBook-Pro:hello-nodejs luminal$ ls | grep app app.js MacBook-Pro:hello-nodejs luminal$ ls | grep app | grep js app.js MacBook-Pro:hello-nodejs luminal$

如果你熟悉linux,那個標準輸入、輸出也是一種流,這個流有很多應用。還有在nodejs中對http請求的處理,也是用流來實現的,請求就是一個輸入流,響應就是一個輸出流。

nodejs官方api有介紹說所有流都是這個EventEmitter事件的一個例項,所以說我們這個流,也可以像事件那樣進行處理。比如說放置一些監聽函式之類的。這個流有很多應用,比如說處理資料。

最典型的就是http服務的時候我們會用到,那個資料的請求和響應就是流的一種體現。還有對資料進行處理,比如說我們流行的構建工具webpack、gulp之類的,它們也大量運用了流這個技術,等下我們會實現檔案打包和壓縮,它也是用流來實現的。

然後流的第二個應用,它就是能提高效能,在前幾篇文章我們有介紹到檔案系統的讀寫命令來讀取檔案內容,但是那些命令它是一次性把檔案放到記憶體中,如果你的檔案很大的時候,那麼這種命令就不太合適了。要用流來處理,這個流的讀取,它會把檔案的內容,放到buffer緩衝區中,它可以一邊放一邊處理,以此提高效能!所以說流有兩個比較好的用途:1、處理資料  2、提高效能。

緩衝區是一塊特定的記憶體區域, 其目的是通過緩解應用程式上下層之間的效能差異,減少上層對下層的等待時間,以此提高系統性能。漏斗是生活中常見的緩衝例子,下層如瓶口等工作效率低,但是上層注水口如水桶工作效率較高,他們之間使用漏斗進行緩衝,用以提高整體的工作效率。這篇博文緩衝區寫的挺好:緩衝(Buffer)_Phoenix_smf的部落格-CSDN部落格

流讀取檔案-createReadStream

下面我們來實現一個流,來體驗一下

``` var fs = require('fs');

//建立讀的流,它是輸入流,讀取一個檔案把它放入流中, //__dirname我們在介紹全域性物件的時候有介紹 var myReadStream = fs.createReadStream(__dirname + '/readMe.txt');

//上面我們有介紹,這個流它是事件的一個例項,所以它有事件的一些特性 //比如繫結一些監聽函式之類的。 myReadStream.on('data', function (chunk) { console.log('new chunk received'); console.log(chunk); }); ```

這個是我的readMe.txt檔案內容,內容是我從nodejs官網摘取下來的

This is in contrast to today's more common concurrency model, in which OS threads are employed. Thread-based networking is relatively inefficient and very difficult to use. Furthermore, users of Node.js are free from worries of dead-locking the process, since there are no locks. Almost no function in Node.js directly performs I/O, so the process never blocks. Because nothing blocks, scalable systems are very reasonable to develop in Node.js

下面我們來看看輸出結果:

MacBook-Pro:hello-nodejs luminal$ node app new chunk received <Buffer 54 68 69 73 20 69 73 20 69 6e 20 63 6f 6e 74 72 61 73 74 20 74 6f 20 74 6f 64 61 79 27 73 20 6d 6f 72 65 20 63 6f 6d 6d 6f 6e 20 63 6f 6e 63 75 72 72 ... >

它並沒有輸出我們檔案的內容,而是輸出類似於這樣的東西,這個Buffer是記憶體中的一塊特定區域,nodejs的流把要讀取的檔案截成一個個Buffer來處理的,也就是它效能提高的原因之一,現在它只有一個Buffer,因為我們的檔案內容比較少。我們把檔案內容全選複製增加一些,5千行左右,我們來試試看一下輸出結果:

acBook-Pro:hello-nodejs luminal$ node app new chunk received <Buffer 54 68 69 73 20 69 73 20 69 6e 20 63 6f 6e 74 72 61 73 74 20 74 6f 20 74 6f 64 61 79 27 73 20 6d 6f 72 65 20 63 6f 6d 6d 6f 6e 20 63 6f 6e 63 75 72 72 ... > new chunk received <Buffer 73 74 65 6d 73 20 61 72 65 20 76 65 72 79 20 72 65 61 73 6f 6e 61 62 6c 65 20 74 6f 20 64 65 76 65 6c 6f 70 20 69 6e 20 4e 6f 64 65 2e 6a 73 0a 0a 20 ... > new chunk received <Buffer 65 76 65 72 20 62 6c 6f 63 6b 73 2e 20 0a 20 42 65 63 61 75 73 65 20 6e 6f 74 68 69 6e 67 20 62 6c 6f 63 6b 73 2c 20 73 63 61 6c 61 62 6c 65 20 73 79 ... > new chunk received <Buffer 69 6f 6e 20 69 6e 20 4e 6f 64 65 2e 6a 73 20 64 69 72 65 63 74 6c 79 20 70 65 72 66 6f 72 6d 73 20 49 2f 4f 2c 20 73 6f 20 74 68 65 20 70 72 6f 63 65 ... > new chunk received <Buffer 74 68 65 20 70 72 6f 63 65 73 73 2c 20 73 69 6e 63 65 20 74 68 65 72 65 20 61 72 65 20 6e 6f 20 6c 6f 63 6b 73 2e 0a 20 41 6c 6d 6f 73 74 20 6e 6f 20 ... > new chunk received <Buffer 75 73 65 72 73 20 6f 66 20 4e 6f 64 65 2e 6a 73 20 61 72 65 20 66 72 65 65 20 66 72 6f 6d 20 77 6f 72 72 69 65 73 20 6f 66 20 64 65 61 64 2d 6c 6f 63 ... > MacBook-Pro:hello-nodejs luminal$

我們會發現輸出很多Buffer,所以說它是分段處理的,就是把檔案截成一個個Buffer來處理的!那麼到底怎麼把檔案讀取出來呢?我們需要加一個編碼格式

``` var fs = require('fs');

//增加引數編碼格式 'utf-8' var myReadStream = fs.createReadStream(__dirname + '/readMe.txt','utf-8');

myReadStream.on('data', function (chunk) { console.log('new chunk received'); console.log(chunk); }); ```

5千行,這個我們就不貼輸出結果啦!

一般來說,我們會這麼寫上面的程式碼,如下:

```

var fs = require('fs');

var myReadStream = fs.createReadStream(__dirname + '/readMe.txt');

myReadStream.setEncoding('utf-8');

var data = "";

myReadStream.on('data', function (chunk) { data += chunk; });

//它還有一個監聽函式,上面'data'是接收資料時候的監聽, //'end'為接收完資料時候的監聽,回撥函式 myReadStream.on('end', function () { console.log(data); }) ```

流寫入檔案-createWriteStream

上面我們講了怎麼用流讀取檔案,下面我講一下用流寫檔案

``` var fs = require('fs');

// 檔案路徑 '/writeMe.txt' 前面的斜槓千萬不要忘了!花了我幾分鐘找bug var myWriteStream = fs.createWriteStream(__dirname + '/writeMe.txt');

var writeData = "hello world"; //寫入資料。引數utf-8可以去掉 myWriteStream.write(writeData, 'utf-8'); //寫入結束 myWriteStream.end(); //寫入完成,回撥方法 myWriteStream.on('finish', function () { console.log('寫入已經完成,finished!'); }); ```

我們來看看輸出結果

MacBook-Pro:hello-nodejs luminal$ node app 寫入已經完成,finished!

這時候你會發現,當前目錄多了一個writeMe.txt 的檔案,裡面的內容是hello world

管道

我們已經用流來實現怎麼讀取和寫入資料到檔案的操作,其實我們可以使用管道來更方便的實現這些操作!

``` var fs = require('fs');

var myReadStream = fs.createReadStream(__dirname + '/readMe.txt'); var myWriteStream = fs.createWriteStream(__dirname + '/writeMe.txt');

myReadStream.pipe(myWriteStream); ```

執行以後,你會發現上面的程式碼直接把 readMe.txt 檔案的內容,寫入到 writeMe.txt 檔案當中!

使用管道的話,程式碼量更少,一行就夠了。myReadStream是讀取的流,myWriteStream是寫入的流

我們之前用過命令: ls | grep app

MacBook-Pro:hello-nodejs luminal$ ls app.js count.js readMe.txt temp MacBook-Pro:hello-nodejs luminal$ ls | grep app app.js MacBook-Pro:hello-nodejs luminal$

其實在這裡myReadStream類似於 ls,pipe管道類似於 |,myWriteStream類似於 grep app

管道例項演示

下面我們來看一個例項,來演示體驗管道的用法

讀取檔案加密壓縮打包

首先我們把  readMe.txt 檔案的內容 手動改為 hello world,然後執行以下壓縮的程式碼

``` // 壓縮 var crypto = require('crypto');//加密庫 var fs = require('fs'); var zlib = require('zlib');//壓縮庫

//(node:32751) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. //Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead. //意思:由於安全性和可用性問題,buffer()已棄用。請改用buffer.alloc()、buffer.allocunsafe()或buffer.from()方法。 // var password = new Buffer(process.env.PASS || 'password');

var password = new Buffer.from(process.env.PASS || 'password'); //參考部落格:https://blog.csdn.net/m0_37263637/article/details/83501928

var encryptStream = crypto.createCipher('aes-256-cbc', password);

var gzip = zlib.createGzip(); var readStream = fs.createReadStream(__dirname + "/readMe.txt"); // current file var writeStream = fs.createWriteStream(__dirname + '/out.gz');

readStream // reads current file .pipe(encryptStream) // encrypts .pipe(gzip) // compresses .pipe(writeStream) // writes to out file .on('finish', function () { // all done console.log('done'); }); ```

我們來看看輸出結果:

MacBook-Pro:hello-nodejs luminal$ node app done MacBook-Pro:hello-nodejs luminal$

這時候當前目錄會生成一個 out.gz 的壓縮檔案!

上面壓縮的程式碼,即讀取readMe.txt 檔案的內容,然後加密打包生成out.gz 的壓縮檔案

讀取檔案解壓解密輸出到終端

我們來看看與上面壓縮程式碼,對應的解壓程式碼

``` // 解壓 var crypto = require('crypto'); var fs = require('fs'); var zlib = require('zlib');

//var password = new Buffer(process.env.PASS || 'password'); var password = new Buffer.from(process.env.PASS || 'password'); var decryptStream = crypto.createDecipher('aes-256-cbc', password);

var gzip = zlib.createGunzip(); var readStream = fs.createReadStream(__dirname + '/out.gz');

readStream // reads current file .pipe(gzip) // uncompresses .pipe(decryptStream) // decrypts .pipe(process.stdout) // writes to terminal .on('finish', function () { // finished console.log('done'); }); ```

我們來看看輸出結果

MacBook-Pro:hello-nodejs luminal$ node app hello worldMacBook-Pro:hello-nodejs luminal$

解壓解密讀取成功!hello world輸出出來了,我們主要演示體驗管道的用法,具體程式碼細節以後會講到!