流(Stream)的优点
流在Node.js中具有很大的吸引力,因为它具有如下优点:
- 高效率(空间、时间)
- 可组合
空间效率
考虑这样一种情况,我们需要从一个很大的文件(几百MB,甚至达到几个GB)中读取数据,用传统的Buffer方式(缓冲)来处理数据,会带来巨大的挑战,很容易导致内存溢出。假如我的机器内存只有1G,但是文件大小却达到了1.5G,那么很明显,就算整个内存用尽也无法一下子缓冲这样大的数据,更何况,机器上的其他应用还需要占用内存用于其他任务的运行。我们来看一个例子:
const { readFile, writeFile } = require('fs/promises')
const { gzip } = require('zlib')
const file = process.argv[2]
readFile(file)
.then(data => {
gzip(data, (err, buffer) => {
writeFile(file + '.gz', buffer)
.then(() => console.log('write over...'))
.catch(e => console.log(e))
})
})
.catch(err => {
console.log(err)
})
将代码保存到gzipBuffer.js,并用如下命令执行:
node gzipBuffer.js hero.mp4
我们使用了一个8G多的大文件hero.mp4来进行压缩时,就报错了:
RangeError [ERR_FS_FILE_TOO_LARGE]: File size (8540621994) is greater than 2 GB
因为文件大小已经超出了V8引擎的最大内存2GB,缓存不下。针对这种情况,如果使用流则可以很好的解决这个问题:
const fs = require('fs')
const zlib = require('zlib')
const file = process.argv[2]
fs.createReadStream(file)
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream(file + '.gz'))
.on('finish', () => console.log('File compressed.'))
对比之下,你会发现,使用流的方式,代码也更清晰。
时间效率
我们通过一个例子来进行说明,下面代码会创建一个服务器,用于接受客户端发送的文件,并对其文件进行压缩处理,最终,写入到服务器的硬盘当中。
const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
const server = http.createServer((req, res) => {
const filename = req.headers.filename
console.log('File request received: ' + filename)
req
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream(filename + '.gz'))
.on('finish', () => {
res.writeHead(201, { 'Content-Type': 'text/plain' })
res.end("That's it\n")
console.log(`File saved: ${filename}`)
})
})
server.listen(3000, () => console.log('Listening...'))
假设客户端发送的文件大小为2M,那么分别使用缓存
和Stream
进行处理的差异如下图所示:
发现其中的区别了吗?
如果使用缓存处理的方式,服务器必须等待来自客户端的文件全部接受完成后,才能进行下一流程(压缩)的处理,这样,每一个流程,就相当于被阻塞
的状态,只有前一个流程彻底完成后才能继续下一个流程,这很低效。
而使用流的方式,服务器只要接受到部分数据
就可以将这部分数据流入到下一个流程(压缩流程),进行压缩处理,并不需要等待所有的数据接收完毕才进行下一个流程。这样,就极大的提高了生产效率,节省了时间。
自由组合
看完上面两个例子,相信你已经观察到了pipe()
这个强大的方法,它可以将不同的处理流程
连接起来,每一个处理流程完成一个单独的小任务,使用这种方式,就可以像玩积木
一样随意地添加和组合其他流。比如,上面的那个例子,如果不需要压缩,则可以将下面代码直接去掉即可:
.pipe(zlib.createGzip())
同时,这种方式,也使得代码便于阅读和维护。
参考
- Mario Casciaro, Luciano Mammino.Node.js 设计模式(第2版)