深入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 03 NodeJs
使用nodejs中httpProxy代理时候出现404异常的解决方法
Aug 15 NodeJs
Nodejs实现短信验证码功能
Feb 09 NodeJs
详解NodeJS框架express的路径映射(路由)功能及控制
Mar 24 NodeJs
深入nodejs中流(stream)的理解
Mar 27 NodeJs
win系统下nodejs环境安装配置
May 04 NodeJs
nodejs构建本地web测试服务器 如何解决访问静态资源问题
Jul 14 NodeJs
Nodejs模块载入运行原理
Feb 23 NodeJs
Nodejs实现WebSocket代码实例
May 19 NodeJs
node快速搭建后台的实现步骤
Feb 18 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
ThinkPHP中Session用法详解
2014/11/29 PHP
Yii使用migrate命令执行sql语句的方法
2016/03/15 PHP
php模式设计之观察者模式应用实例分析
2019/09/25 PHP
Jquery UI震动效果实现原理及步骤
2013/02/04 Javascript
JS中showModalDialog 的使用解析
2013/04/17 Javascript
解析jQuery与其它js(Prototype)库兼容共存
2013/07/04 Javascript
枚举的实现求得1-1000所有出现1的数字并计算出现1的个数
2013/09/10 Javascript
JavaScript定时显示广告代码分享
2015/03/02 Javascript
jquery密码强度校验
2015/12/02 Javascript
js实现瀑布流的三种方式比较
2020/06/28 Javascript
jQuery图片旋转插件jQueryRotate.js用法实例(附demo下载)
2016/01/21 Javascript
iscroll碰到Select无法选择下拉刷新的解决办法
2016/05/21 Javascript
vue项目首屏打开速度慢的解决方法
2019/03/31 Javascript
Vue CL3 配置路径别名详解
2019/05/30 Javascript
vue 移动端注入骨架屏的配置方法
2019/06/25 Javascript
JS实现简单tab选项卡切换
2019/10/25 Javascript
vue实现浏览器全屏展示功能
2019/11/27 Javascript
微信小程序图片加载失败时替换为默认图片的方法
2019/12/09 Javascript
JS实现移动端双指缩放和旋转方法
2019/12/13 Javascript
利用js canvas实现五子棋游戏
2020/10/11 Javascript
Python操作使用MySQL数据库的实例代码
2017/05/25 Python
Python图像的增强处理操作示例【基于ImageEnhance类】
2019/01/03 Python
10 行Python 代码实现 AI 目标检测技术【推荐】
2019/06/14 Python
详解python实现小波变换的一个简单例子
2019/07/18 Python
Python3+Requests+Excel完整接口自动化测试框架的实现
2019/10/11 Python
Python嵌入C/C++进行开发详解
2020/06/09 Python
马来西亚网上购物平台:ezbuy
2018/02/13 全球购物
GANT葡萄牙官方商店:拥有美国运动服传统的生活方式品牌
2018/10/18 全球购物
Manuka Doctor英国官网:真正的麦卢卡蜂蜜和护肤品
2018/10/26 全球购物
计算机应用职专应届生求职信
2013/11/12 职场文书
会计专业自我评价
2014/02/12 职场文书
硕士研究生就业推荐信
2014/05/18 职场文书
和谐家庭演讲稿
2014/05/24 职场文书
2014年度党员自我评议
2014/09/13 职场文书
人事行政助理岗位职责
2015/04/11 职场文书
小学语文新课改心得体会
2016/01/22 职场文书