为何选择 Streams
让我们检查以下两个用于读取文件内容的示例:
第一个,它使用异步方法读取文件,并提供一个回调函数,一旦文件完全读入内存就调用该函数:
fs.readFile(`${__dirname}/utils.js`, (err, data) => {
if (err) {
handleError(err);
} else {
console.log(data.toString());
}
})
第二个,它使用 streams
来逐个读取文件的内容:
var fileStream = fs.createReadStream(`${__dirname}/file`);
var fileContent = '';
fileStream.on('data', data => {
fileContent += data.toString();
})
fileStream.on('end', () => {
console.log(fileContent);
})
fileStream.on('error', err => {
handleError(err)
})
值得一提的是,这两个例子完全相同。那有什么区别呢?
- 第一个更短,看起来更优雅
- 第二个让你做一些处理上的文件而被阅读(!)
当你处理的文件很小时,使用 streams
时没有实际效果,但是当文件很大时会发生什么? (这么大,需要 10 秒才能将其读入内存)
如果没有 streams
,你将等待,绝对不做任何事情(除非你的进程做其他事情),直到 10 秒钟通过并且文件被完全读取,然后才能开始处理文件。
使用 streams
,你可以一目了然地获取文件的内容,只要它们可用 - 并且可以让你在读取文件时处理该文件。
上面的例子没有说明如何将 streams
用于进行回调方式时无法完成的工作,所以让我们看另一个例子:
我想下载一个 gzip
文件,解压缩并将其内容保存到磁盘。鉴于文件的 url
,这是需要做的:
- 下载文件
- 解压缩文件
- 将其保存到磁盘
这是一个[小文件] [1],存储在我的 S3
存储中。以下代码以回调方式执行上述操作。
var startTime = Date.now()
s3.getObject({Bucket: 'some-bucket', Key: 'tweets.gz'}, (err, data) => {
// here, the whole file was downloaded
zlib.gunzip(data.Body, (err, data) => {
// here, the whole file was unzipped
fs.writeFile(`${__dirname}/tweets.json`, data, err => {
if (err) console.error(err)
// here, the whole file was written to disk
var endTime = Date.now()
console.log(`${endTime - startTime} milliseconds`) // 1339 milliseconds
})
})
})
// 1339 milliseconds
这是使用 streams
看起来的样子:
s3.getObject({Bucket: 'some-bucket', Key: 'tweets.gz'}).createReadStream()
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream(`${__dirname}/tweets.json`));
// 1204 milliseconds
是的,处理小文件时速度并不快 - 测试文件权重 80KB
。在一个更大的文件上测试这个,71MB
gzipped(382MB
解压缩),表明 streams
版本更快
- 下载
71MB
,解压缩然后将382MB
写入磁盘花了 20925 毫秒 - 使用回调方式。 - 相比之下,使用
streams
版本时需要 13434 毫秒才能完成相同的操作(对于不那么大的文件,速度提高 35%)