深入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 相关文章推荐
Google官方支持的NodeJS访问API,提供后台登录授权
Jul 29 NodeJs
nodejs教程之异步I/O
Nov 21 NodeJs
Nodejs学习笔记之Global Objects全局对象
Jan 13 NodeJs
NodeJS与HTML5相结合实现拖拽多个文件上传到服务器的实现方法
Jul 26 NodeJs
Nodejs进阶:基于express+multer的文件上传实例
Nov 21 NodeJs
nodejs中向HTTP响应传送进程的输出
Mar 19 NodeJs
NodeJs的fs读写删除移动监听
Apr 28 NodeJs
nodejs集成sqlite使用示例
Jun 05 NodeJs
详解NODEJS的http实现
Jan 04 NodeJs
用Electron写个带界面的nodejs爬虫的实现方法
Jan 29 NodeJs
Nodejs libuv运行原理详解
Aug 21 NodeJs
浅谈JS和Nodejs中的事件驱动
May 05 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中计算中文字符串长度、截取中文字符串的函数代码
2011/08/09 PHP
PHP操作Memcache实例介绍
2013/06/14 PHP
php实现RSA加密类实例
2015/03/26 PHP
PHP+redis实现的限制抢购防止商品超发功能详解
2019/09/19 PHP
laravel框架之数据库查出来的对象实现转化为数组
2019/10/23 PHP
JavaScript 检测浏览器和操作系统的脚本
2008/12/26 Javascript
Jquery之Ajax运用 学习运用篇
2011/09/26 Javascript
jquery中ajax学习笔记3
2011/10/16 Javascript
js multiple全选与取消全选实现代码
2012/12/04 Javascript
IE网页js语法错误2行字符1、FF中正常的解决方法
2013/09/09 Javascript
js清除input中type等于file的值域(示例代码)
2013/12/24 Javascript
jQuery中:not选择器用法实例
2014/12/30 Javascript
bootstrap基础知识学习笔记
2016/11/02 Javascript
深入理解 JavaScript 中的 JSON
2017/04/06 Javascript
深入理解JavaScript 参数按值传递
2017/05/24 Javascript
微信小程序movable view移动图片和双指缩放实例代码
2017/08/08 Javascript
Vue+Element使用富文本编辑器的示例代码
2017/08/14 Javascript
小程序云开发之用户注册登录
2019/05/18 Javascript
layui使用button按钮 点击出现弹层 弹层中加载表单的实例
2019/09/04 Javascript
详解element-ui级联菜单(城市三级联动菜单)和回显问题
2019/10/02 Javascript
详解node登录接口之密码错误限制次数(含代码)
2019/10/25 Javascript
详解Python中的条件判断语句
2015/05/14 Python
python魔法方法-属性访问控制详解
2016/07/25 Python
python 实现红包随机生成算法的简单实例
2017/01/04 Python
opencv python 基于KNN的手写体识别的实例
2018/08/03 Python
对python中的高效迭代器函数详解
2018/10/18 Python
pandas中apply和transform方法的性能比较及区别介绍
2018/10/30 Python
使用Python的datetime库处理时间(RPA流程)
2019/11/24 Python
Python 实现训练集、测试集随机划分
2020/01/08 Python
Python中logging日志库实例详解
2020/02/19 Python
keras 读取多标签图像数据方式
2020/06/12 Python
explicit和implicit的含义
2012/11/15 面试题
CSS代码检查工具stylelint的使用方法详解
2021/03/27 HTML / CSS
学生宿舍管理制度
2014/01/30 职场文书
2014乡党委副书记党建工作汇报材料
2014/11/02 职场文书
Mysql数据库命令大全
2021/05/26 MySQL