Node.js后端开发 - 基础篇 #8 流和管道

文章目录

流-简单介绍

流读取文件-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输出出来了,我们主要演示体验管道的用法,具体代码细节以后会讲到!

猜你喜欢

转载自juejin.im/post/7110236091827322894