深入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服务器(1):一个简单nodejs服务器例子
Dec 18 NodeJs
NodeJS学习笔记之(Url,QueryString,Path)模块
Jan 13 NodeJs
nodejs实现HTTPS发起POST请求
Apr 23 NodeJs
nodeJs爬虫获取数据简单实现代码
Mar 29 NodeJs
NodeJS整合银联网关支付(DEMO)
Nov 09 NodeJs
详解nodejs 文本操作模块-fs模块(五)
Dec 23 NodeJs
Nodejs多站点切换Htpps协议详解及简单实例
Feb 23 NodeJs
nodejs创建简易web服务器与文件读写的实例
Sep 07 NodeJs
NodeJS父进程与子进程资源共享原理与实现方法
Mar 16 NodeJs
解决nodejs的npm命令无反应的问题
May 17 NodeJs
nodejs之koa2请求示例(GET,POST)
Aug 07 NodeJs
NodeJs 文件系统操作模块fs使用方法详解
Nov 26 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与XML的PDF文档生成技术
2006/10/09 PHP
在服务端进行目录建立、删除,文件上传、删除的过程的php代码
2008/09/10 PHP
用php实现百度网盘图片直链的代码分享
2012/11/01 PHP
使用Apache的htaccess防止图片被盗链的解决方法
2013/04/27 PHP
php文件服务实现虚拟挂载其他目录示例
2014/04/17 PHP
用php守护另一个php进程的例子
2015/02/13 PHP
PHP结合jQuery.autocomplete插件实现输入自动完成提示的功能
2015/04/27 PHP
详谈php静态方法及普通方法的区别
2016/10/04 PHP
ThinkPHP框架获取最后一次执行SQL语句及变量调试简单操作示例
2018/06/13 PHP
Document 对象的常用方法
2009/07/31 Javascript
基于jquery的无缝循环新闻列表插件
2011/03/07 Javascript
jQuery如何将选中的对象转化为原始的DOM对象
2014/06/09 Javascript
js实现ArrayList功能附实例代码
2014/10/29 Javascript
express的中间件bodyParser详解
2014/12/04 Javascript
jQuery mobile在页面加载时添加加载中效果 document.ready 和window.onload执行顺序比较
2016/07/14 Javascript
vue2.0 中#$emit,$on的使用详解
2017/06/07 Javascript
JS 实现banner图片轮播效果(鼠标事件)
2017/08/04 Javascript
浅谈箭头函数写法在ReactJs中的使用
2017/08/22 Javascript
Three.js基础学习教程
2017/11/16 Javascript
webpack打包js的方法
2018/03/12 Javascript
tracking.js页面人脸识别插件使用方法
2020/04/16 Javascript
JS函数节流和防抖之间的区分和实现详解
2019/01/11 Javascript
解决Layui中layer报错的问题
2019/09/03 Javascript
Python中使用Beautiful Soup库的超详细教程
2015/04/30 Python
详解Django定时任务模块设计与实践
2019/07/24 Python
python上传时包含boundary时的解决方法
2020/04/08 Python
Python3 socket即时通讯脚本实现代码实例(threading多线程)
2020/06/01 Python
Glamest意大利:女性在线奢侈品零售店
2019/04/28 全球购物
澳大利亚最受欢迎的美发用品目的地:AMR
2019/08/28 全球购物
惠而浦美国官网:Whirlpool.com
2021/01/19 全球购物
企业给企业的表扬信
2014/01/13 职场文书
慈善捐赠倡议书
2014/08/30 职场文书
民间个人借款协议书
2014/09/30 职场文书
2015年营业员工作总结
2015/04/23 职场文书
创建文明城市倡议书
2015/04/28 职场文书
各国货币符号大全
2022/02/17 杂记