nodejs中流(stream)的理解

秒速五厘米 2022-06-07 07:49 283阅读 0赞

#

nodejs的fs模块并没有提供一个copy的方法,但我们可以很容易的实现一个,比如:

  1. var source = fs.readFileSync('/path/to/source', {
  2. encoding: 'utf8'});
  3. fs.writeFileSync('/path/to/dest', source);

这种方式是把文件内容全部读入内存,然后再写入文件,对于小型的文本文件,这没有多大问题,比如grunt-file-copy就是这样实现的。但是对于体积较大的二进制文件,比如音频、视频文件,动辄几个GB大小,如果使用这种方法,很容易使内存“爆仓”。理想的方法应该是读一部分,写一部分,不管文件有多大,只要时间允许,总会处理完成,这里就需要用到流的概念。

bVcla6

如上面高大上的图片所示,我们把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(pipe)连接两个桶使得水从一个桶流入另一个桶,这样就慢慢的实现了大文件的复制过程。

Stream在nodejs中是EventEmitter的实现,并且有多种实现形式,例如:

  • http responses request
  • fs read write streams
  • zlib streams
  • tcp sockets
  • child process stdout and stderr

上面的文件复制可以简单实现一下:

  1. var fs = require('fs'); var readStream = fs.createReadStream('/path/to/source'); var writeStream = fs.createWriteStream('/path/to/dest'); readStream.on('data', function(chunk) { // 当有数据流出时,写入数据 writeStream.write(chunk); }); readStream.on('end', function() { // 当没有数据时,关闭数据流 writeStream.end(); });

上面的写法有一些问题,如果写入的速度跟不上读取的速度,有可能导致数据丢失。正常的情况应该是,写完一段,再读取下一段,如果没有写完的话,就让读取流先暂停,等写完再继续,于是代码可以修改为:

  1. var fs = require('fs'); var readStream = fs.createReadStream('/path/to/source'); var writeStream = fs.createWriteStream('/path/to/dest'); readStream.on('data', function(chunk) { // 当有数据流出时,写入数据 if (writeStream.write(chunk) === false) { // 如果没有写完,暂停读取流 readStream.pause(); } }); writeStream.on('drain', function() { // 写完后,继续读取 readStream.resume(); }); readStream.on('end', function() { // 当没有数据时,关闭数据流 writeStream.end(); });

或者使用更直接的pipe

  1. // pipe自动调用了data,end等事件
  2. fs.createReadStream('/path/to/source').pipe(fs.createWriteStream('/path/to/dest'));

下面是一个更加完整的复制文件的过程

  1. var fs = require('fs'),
  2. path = require('path'),
  3. out = process.stdout;
  4. var filePath = '/Users/chen/Movies/Game.of.Thrones.S04E07.1080p.HDTV.x264-BATV.mkv';
  5. var readStream = fs.createReadStream(filePath);
  6. var writeStream = fs.createWriteStream('file.mkv');
  7. var stat = fs.statSync(filePath);
  8. var totalSize = stat.size;
  9. var passedLength = 0;
  10. var lastSize = 0;
  11. var startTime = Date.now();
  12. readStream.on('data', function(chunk) {
  13. passedLength += chunk.length;
  14. if (writeStream.write(chunk) === false) {
  15. readStream.pause();
  16. }
  17. });
  18. readStream.on('end', function() {
  19. writeStream.end();
  20. });
  21. writeStream.on('drain', function() {
  22. readStream.resume();
  23. });
  24. setTimeout(function show() {
  25. var percent = Math.ceil((passedLength / totalSize) * 100);
  26. var size = Math.ceil(passedLength / 1000000);
  27. var diff = size - lastSize;
  28. lastSize = size;
  29. out.clearLine();
  30. out.cursorTo(0);
  31. out.write('已完成' + size + 'MB, ' + percent + '%, 速度:' + diff * 2 + 'MB/s');
  32. if (passedLength < totalSize) {
  33. setTimeout(show, 500);
  34. } else {
  35. var endTime = Date.now();
  36. console.log();
  37. console.log('共用时:' + (endTime - startTime) / 1000 + '秒。');
  38. }
  39. }, 500);

可以把上面的代码保存为copy.js试验一下

我们添加了一个递归的setTimeout(或者直接使用setInterval)来做一个旁观者,每500ms观察一次完成进度,并把已完成的大小、百分比和复制速度一并写到控制台上,当复制完成时,计算总的耗费时间,效果如图:

bVclbf

我们复制了一集1080p的权利的游戏第四季第7集,大概3.78G大小,由于使用了SSD,可以看到速度还是非常不错的,哈哈哈~
复制完成后,显示总花费时间

bVclbi

结合nodejs的readlineprocess.argv等模块,我们可以添加覆盖提示、强制覆盖、动态指定文件路径等完整的复制方法,有兴趣的可以实现一下,实现完成,可以

  1. ln -s /path/to/copy.js /usr/local/bin/mycopy

这样就可以使用自己写的mycopy命令替代系统的cp命令

文章来源:https://segmentfault.com/a/1190000000519006

发表评论

表情:
评论列表 (有 0 条评论,283人围观)

还没有评论,来说两句吧...

相关阅读

    相关 NodeJS Stream

    什么是 Stream 在 Unix 系统中流就是一个很常见也很重要的概念,从术语上讲流是对输入输出设备的抽象。 ls | grep .js 类似这样的代

    相关 nodejs stream

    今天起得晚,发现我的博客排名上升到5位数了,激动之情难以言表,只有再写一篇博客来巩固自己的地位(以上内容与本文无关,轻喷~) 之前说过,nodejs操

    相关 初探nodejsstream(流)

    什么是流? `Stream(流)` 就和它得名字一样,我们可以把它想象成像水流,从一个地方流向另外一个地方。按照一定速率,有快有慢。而Nodejs里流则是将数据以一定的速