深入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、Python写的一个简易HTTP静态文件服务器
Jul 18 NodeJs
Nodejs极简入门教程(一):模块机制
Oct 25 NodeJs
nodejs教程之异步I/O
Nov 21 NodeJs
nodejs批量修改文件编码格式
Jan 22 NodeJs
nodejs的HTML分析利器node-jquery用法浅析
Nov 08 NodeJs
nodejs redis 发布订阅机制封装实现方法及实例代码
Dec 15 NodeJs
详解HTTPS 的原理和 NodeJS 的实现
Jul 04 NodeJs
nodejs操作mongodb的填删改查模块的制作及引入实例
Jan 02 NodeJs
nodejs+mongodb aggregate级联查询操作示例
Mar 17 NodeJs
Nodejs把接收图片base64格式保存为文件存储到服务器上
Sep 26 NodeJs
基于nodejs的微信JS-SDK简单应用实现
May 21 NodeJs
NodeJs 模仿SIP话机注册的方法
Jun 21 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之规范编程命名小结
2013/05/15 PHP
php json与xml序列化/反序列化
2013/10/28 PHP
解决file_get_contents无法请求https连接的方法
2013/12/17 PHP
php学习笔记之基础知识
2014/11/08 PHP
Yii 框架控制器创建使用及控制器响应操作示例
2019/10/14 PHP
PHP的垃圾回收机制代码实例讲解
2021/02/27 PHP
来自chinaz的ajax获取评论代码
2008/05/03 Javascript
异步加载script的代码
2011/01/12 Javascript
Javascript中的异步编程规范Promises/A详细介绍
2014/06/06 Javascript
iframe里的页面禁止右键事件的方法
2014/06/10 Javascript
EasyUI中实现form表单提交的示例分享
2015/03/01 Javascript
JavaScript基础知识及常用方法总结
2016/01/10 Javascript
如何判断Javascript对象是否存在的简单实例
2016/05/18 Javascript
jQuery实现图片轮播效果代码(基于jquery.pack.js插件)
2016/06/02 Javascript
Angular.JS实现无限级的联动菜单(使用demo)
2017/02/08 Javascript
vue+iview+less+echarts实战项目总结
2018/02/22 Javascript
layui文件上传控件带更改后数据传值的方法
2019/09/23 Javascript
python字符串过滤性能比较5种方法
2017/06/22 Python
python3连接kafka模块pykafka生产者简单封装代码
2019/12/23 Python
python批量合成bilibili的m4s缓存文件为MP4格式 ver2.5
2020/12/01 Python
Python爬虫后获取重定向url的两种方法
2021/01/19 Python
ECHT官方网站:男女健身服
2020/02/14 全球购物
Tomcat中怎么使用log4j输出所有的log
2016/07/07 面试题
自动化专业本科毕业生求职信
2013/10/20 职场文书
企业晚会策划方案
2014/05/29 职场文书
奥林匹克的口号
2014/06/13 职场文书
解除劳动合同协议书
2014/09/17 职场文书
婚宴邀请函
2015/01/30 职场文书
仓库统计员岗位职责
2015/04/14 职场文书
上帝也疯狂观后感
2015/06/09 职场文书
2016习总书记系列重要讲话心得体会
2016/01/15 职场文书
少儿励志名言(80句)
2019/08/14 职场文书
研究生学习计划书应该怎么写?
2019/09/10 职场文书
OpenCV-Python实现图像平滑处理操作
2021/06/08 Python
MySQL如何快速创建800w条测试数据表
2022/03/17 MySQL
电脑关机速度很慢怎么办 提升电脑关机速度设置教程
2022/04/08 数码科技