深入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实用示例 缩址还原
Dec 28 NodeJs
nodejs npm包管理的配置方法及常用命令介绍
Jun 05 NodeJs
nodejs分页类代码分享
Jun 17 NodeJs
Nodejs关于gzip/deflate压缩详解
Mar 04 NodeJs
Nodejs爬虫进阶教程之异步并发控制
Feb 15 NodeJs
nodejs的压缩文件模块archiver用法示例
Jan 18 NodeJs
详解nodeJS之路径PATH模块
May 31 NodeJs
基于nodejs 的多页面爬虫实例代码
May 31 NodeJs
浅谈nodejs中的类定义和继承的套路
Jul 26 NodeJs
nodejs 图片预览和上传的示例代码
Sep 30 NodeJs
nodeJs爬虫的技术点总结
May 13 NodeJs
nodejs require js文件入口,在package.json中指定默认入口main方法
Oct 10 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
ezSQL PHP数据库操作类库
2010/05/16 PHP
php中static静态变量的使用方法详解
2010/06/04 PHP
php的chr和ord函数实现字符加减乘除运算实现代码
2011/12/05 PHP
基于PHP Web开发MVC框架的Smarty使用说明
2013/04/19 PHP
php将textarea数据提交到mysql出现很多空格的解决方法
2014/12/19 PHP
PHP实践教程之过滤、验证、转义与密码详解
2017/07/24 PHP
php实现微信小程序授权登录功能(实现流程)
2019/11/13 PHP
PHP高并发和大流量解决方案整理
2021/03/09 PHP
JavaScript 编程引入命名空间的方法
2007/06/29 Javascript
jQuery EasyUI API 中文文档 - Parser 解析器
2011/09/29 Javascript
JavaScript实现的购物车效果可以运用在好多地方
2014/05/09 Javascript
JavaScript函数参数使用带参数名的方式赋值传入的方法
2015/03/19 Javascript
Bootstrap导航栏各元素操作方法(表单、按钮、文本)
2015/12/28 Javascript
jQuery+CSS3实现仿花瓣网固定顶部位置带悬浮效果的导航菜单
2016/09/21 Javascript
Jquery 整理元素选取、常用方法一览表
2016/11/26 Javascript
jQuery简单实现遍历单选框的方法
2017/03/06 Javascript
妙用Angularjs实现表格按指定列排序
2017/06/23 Javascript
Python字符串拼接六种方法介绍
2017/12/18 Python
Python类和对象的定义与实际应用案例分析
2018/12/27 Python
Python 互换字典的键值对实例
2019/02/12 Python
Pytorch.nn.conv2d 过程验证方式(单,多通道卷积过程)
2020/01/03 Python
Python3.9又更新了:dict内置新功能
2020/02/28 Python
pandas 像SQL一样使用WHERE IN查询条件说明
2020/06/05 Python
OpenCV 使用imread()函数读取图片的六种正确姿势
2020/07/09 Python
selenium如何定位span元素的实现
2021/01/13 Python
吃透移动端 Html5 响应式布局
2019/12/16 HTML / CSS
新秀丽拉杆箱美国官方网站:Samsonite美国
2016/07/25 全球购物
Linux的文件类型
2012/03/07 面试题
生产副总岗位职责
2013/11/28 职场文书
九月份红领巾广播稿
2014/01/22 职场文书
售后服务承诺书
2014/03/26 职场文书
个人工作保证书
2015/02/28 职场文书
写给女朋友的保证书
2015/05/09 职场文书
Nginx 502 Bad Gateway错误原因及解决方案
2021/03/31 Servers
Spring Cloud Gateway去掉url前缀
2021/07/15 Java/Android
Java处理延时任务的常用几种解决方案
2022/06/01 Java/Android