深入nodejs中流(stream)的理解


Posted in NodeJs onMarch 27, 2017

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

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

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

深入nodejs中流(stream)的理解

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

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

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

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

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();
});

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

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

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

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

var fs = require('fs'),
  path = require('path'),
  out = process.stdout;

var filePath = '/Users/chen/Movies/Game.of.Thrones.S04E07.1080p.HDTV.x264-BATV.mkv';

var readStream = fs.createReadStream(filePath);
var writeStream = fs.createWriteStream('file.mkv');

var stat = fs.statSync(filePath);

var totalSize = stat.size;
var passedLength = 0;
var lastSize = 0;
var startTime = Date.now();

readStream.on('data', function(chunk) {

  passedLength += chunk.length;

  if (writeStream.write(chunk) === false) {
    readStream.pause();
  }
});

readStream.on('end', function() {
  writeStream.end();
});

writeStream.on('drain', function() {
  readStream.resume();
});

setTimeout(function show() {
  var percent = Math.ceil((passedLength / totalSize) * 100);
  var size = Math.ceil(passedLength / 1000000);
  var diff = size - lastSize;
  lastSize = size;
  out.clearLine();
  out.cursorTo(0);
  out.write('已完成' + size + 'MB, ' + percent + '%, 速度:' + diff * 2 + 'MB/s');
  if (passedLength < totalSize) {
    setTimeout(show, 500);
  } else {
    var endTime = Date.now();
    console.log();
    console.log('共用时:' + (endTime - startTime) / 1000 + '秒。');
  }
}, 500);

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

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

深入nodejs中流(stream)的理解

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

深入nodejs中流(stream)的理解

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

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
golang、python、php、c++、c、java、Nodejs性能对比
Mar 12 NodeJs
基于NodeJS的前后端分离的思考与实践(三)轻量级的接口配置建模框架
Sep 26 NodeJs
学习 NodeJS 第八天:Socket 通讯实例
Dec 21 NodeJs
Nodejs实现短信验证码功能
Feb 09 NodeJs
详解nodeJS之二进制buffer对象
Jun 03 NodeJs
Express+Nodejs 下的登录拦截实现代码
Jul 01 NodeJs
理解nodejs的stream和pipe机制的原理和实现
Aug 12 NodeJs
详解NODEJS基于FFMPEG视频推流测试
Nov 17 NodeJs
nodejs结合Socket.IO实现的即时通讯功能详解
Jan 12 NodeJs
nodejs acl的用户权限管理详解
Mar 14 NodeJs
nodejs(officegen)+vue(axios)在客户端导出word文档的方法
Jul 31 NodeJs
nodejs+express最简易的连接数据库的方法
Dec 23 NodeJs
nodejs学习笔记之路由
Mar 27 #NodeJs
NodeJS处理Express中异步错误
Mar 26 #NodeJs
简单好用的nodejs 爬虫框架分享
Mar 26 #NodeJs
nodejs开发——express路由与中间件
Mar 24 #NodeJs
详解NodeJS框架express的路径映射(路由)功能及控制
Mar 24 #NodeJs
NodeJS学习笔记之Module的简介
Mar 24 #NodeJs
详解nodejs中的process进程
Mar 19 #NodeJs
You might like
PHP下对数组进行排序的函数
2010/08/08 PHP
php中hashtable实现示例分享
2014/02/13 PHP
php实现读取超大文件的方法
2014/07/28 PHP
CodeIgniter配置之SESSION用法实例分析
2016/01/19 PHP
jquery监控数据是否变化(修正版)
2011/04/12 Javascript
在HTML中插入JavaScript代码的示例
2015/06/03 Javascript
第一章之初识Bootstrap
2016/04/25 Javascript
javascript中活灵活现的Array对象详解
2016/11/30 Javascript
JavaScript用JSONP跨域请求数据实例详解
2017/01/06 Javascript
jQuery插件FusionWidgets实现的Cylinder图效果示例【附demo源码】
2017/03/23 jQuery
JavaScript 实现 Tab 点击切换实例代码
2017/03/25 Javascript
在node中如何使用 ES6
2017/04/22 Javascript
vue element动态渲染、移除表单并添加验证的实现
2019/01/16 Javascript
Nodejs 识别图片类型的方法
2019/08/15 NodeJs
JavaScript React如何修改默认端口号方法详解
2020/07/28 Javascript
进一步探究Python中的正则表达式
2015/04/28 Python
Python基于PyGraphics包实现图片截取功能的方法
2017/12/21 Python
详解如何在python中读写和存储matlab的数据文件(*.mat)
2018/02/24 Python
Python中将两个或多个list合成一个list的方法小结
2019/05/12 Python
python pandas 时间日期的处理实现
2019/07/30 Python
python pip安装包出现:Failed building wheel for xxx错误的解决
2019/12/25 Python
python使用rsa非对称加密过程解析
2019/12/28 Python
Python TCPServer 多线程多客户端通信的实现
2019/12/31 Python
使用Python 自动生成 Word 文档的教程
2020/02/13 Python
一篇文章带你学习CSS3图片边框
2020/11/04 HTML / CSS
英国时尚优质的女装:Hope Fashion
2018/08/14 全球购物
美国最大婚纱连锁店运营商:David’s Bridal
2019/03/12 全球购物
哈理工毕业生的求职信
2013/12/22 职场文书
不拖欠农民工工资承诺书
2014/03/31 职场文书
百日安全活动总结
2014/05/04 职场文书
环保倡议书格式范文
2014/05/14 职场文书
乡镇群众路线教育实践活动整改措施
2014/10/04 职场文书
人为什么会“幸灾乐祸”?
2019/08/06 职场文书
导游词之山东八大关
2019/12/18 职场文书
解决Tkinter中button按钮未按却主动执行command函数的问题
2021/05/23 Python
python前后端自定义分页器
2022/04/13 Python