深入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 相关文章推荐
nodejs win7下安装方法
May 24 NodeJs
NodeJS制作爬虫全过程
Dec 22 NodeJs
NodeJS中Buffer模块详解
Jan 07 NodeJs
nodejs爬虫遇到的乱码问题汇总
Apr 07 NodeJs
windows系统下更新nodejs版本的方案
Nov 24 NodeJs
详解nodejs通过响应回写的方式渲染页面资源
Apr 07 NodeJs
webstorm中配置nodejs环境及npm的实例
May 15 NodeJs
nodejs微信开发之接入指南
Mar 17 NodeJs
使用nodejs分离html文件里的js和css详解
Apr 12 NodeJs
使用nodejs实现JSON文件自动转Excel的工具(推荐)
Jun 24 NodeJs
使用 Koa + TS + ESLlint 搭建node服务器的过程详解
May 30 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
Terran热键控制
2020/03/14 星际争霸
PHP 加密与解密的斗争
2009/04/17 PHP
解析crontab php自动运行的方法
2013/06/24 PHP
php 模拟post_验证页面的返回状态(实例讲解)
2013/10/28 PHP
php实现的验证码文件类实例
2015/06/18 PHP
十个PHP高级应用技巧果断收藏
2015/09/25 PHP
prototype Element学习笔记(篇二)
2008/10/26 Javascript
javascript仿php的print_r函数输出json数据
2013/09/13 Javascript
JQuery中$(document)是什么意思有什么作用
2014/07/21 Javascript
jQuery实现自动调整字体大小的方法
2015/06/15 Javascript
javascript作用域问题实例分析
2015/07/13 Javascript
11种ASP连接数据库的方法
2015/09/18 Javascript
Hammer.js+轮播原理实现简洁的滑屏功能
2016/02/02 Javascript
JS文件/图片从电脑里面拖拽到浏览器上传文件/图片
2017/03/08 Javascript
Bootstrap常用组件学习(整理)
2017/03/24 Javascript
nodejs mysql 实现分页的方法
2017/06/06 NodeJs
ES6使用Set数据结构实现数组的交集、并集、差集功能示例
2017/10/31 Javascript
详解Vue-Router源码分析路由实现原理
2019/05/15 Javascript
es6函数之尾递归用法实例分析
2020/04/25 Javascript
Openlayers3实现车辆轨迹回放功能
2020/09/29 Javascript
python实现汉诺塔方法汇总
2016/07/25 Python
解决python3 urllib中urlopen报错的问题
2017/03/25 Python
python3处理含有中文的url方法
2018/05/10 Python
Python 读取某个目录下所有的文件实例
2018/06/23 Python
python实现移位加密和解密
2019/03/22 Python
Pyqt5实现英文学习词典
2019/06/24 Python
Python实现遗传算法(二进制编码)求函数最优值方式
2020/02/11 Python
Watch Station官方网站:世界一流的手表和智能手表
2020/01/05 全球购物
戴尔荷兰官方网站:Dell荷兰
2020/10/04 全球购物
2019史上最全Database工程师题库
2015/12/06 面试题
2013的个人自我评价
2013/12/26 职场文书
婚前保证书
2014/04/29 职场文书
2014幼儿园中班工作总结
2014/11/10 职场文书
教师节简报
2015/07/20 职场文书
2016年主题党日活动总结
2016/04/05 职场文书
解读MySQL的客户端和服务端协议
2021/05/10 MySQL