深入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学习笔记之Http模块
Jan 13 NodeJs
使用NodeJs 开发微信公众号(三)微信事件交互实例
Mar 02 NodeJs
Nodejs如何搭建Web服务器
Mar 28 NodeJs
nodejs+express搭建多人聊天室步骤
Feb 12 NodeJs
nodejs之koa2请求示例(GET,POST)
Aug 07 NodeJs
webpack打包nodejs项目的方法
Sep 26 NodeJs
nodejs更新package.json中的dependencies依赖到最新版本的方法
Oct 10 NodeJs
Nodejs中获取当前函数被调用的行数及文件名详解
Dec 12 NodeJs
NodeJs生成sitemap站点地图的方法示例
Jun 11 NodeJs
Nodejs libuv运行原理详解
Aug 21 NodeJs
nodejs各种姿势断点调试的方法
Jun 18 NodeJs
分享node.js实现简单登录注册的具体代码
Apr 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 什么是PEAR?(第三篇)
2009/03/19 PHP
PHP计划任务、定时执行任务的实现代码
2011/04/23 PHP
phpword插件导出word文件时中文乱码问题处理方案
2014/08/19 PHP
php实现获取文件mime类型的方法
2015/02/11 PHP
PHP 中常量的知识整理
2017/04/14 PHP
JavaScript null和undefined区别分析
2009/10/14 Javascript
Javascript 页面模板化很多人没有使用过的方法
2012/06/05 Javascript
Javascript call和apply区别及使用方法
2013/11/14 Javascript
JavaScript中rem布局在react中的应用
2015/12/09 Javascript
谈谈JavaScript的New关键字
2016/08/26 Javascript
Vuejs第十篇之vuejs父子组件通信
2016/09/06 Javascript
使用Ajax生成的Excel文件并下载的实例
2016/11/21 Javascript
assert()函数用法总结(推荐)
2017/01/25 Javascript
JavaScript定时器制作弹窗小广告
2017/02/05 Javascript
JS完成画圆圈的小球
2017/03/07 Javascript
微信小程序之网络请求简单封装实例详解
2017/06/28 Javascript
vue axios整合使用全攻略
2018/05/24 Javascript
使用Vue生成动态表单
2019/11/26 Javascript
vue实现移动端input上传视频、音频
2020/08/18 Javascript
使用Python &amp; Flask 实现RESTful Web API的实例
2017/09/19 Python
python中 logging的使用详解
2017/10/25 Python
python爬虫之urllib3的使用示例
2018/07/09 Python
Python语言快速上手学习方法
2018/12/14 Python
python中如何使用分步式进程计算详解
2019/03/22 Python
Python中SQLite如何使用
2020/05/27 Python
keras分类之二分类实例(Cat and dog)
2020/07/09 Python
Python定义一个Actor任务
2020/07/29 Python
Python grequests模块使用场景及代码实例
2020/08/10 Python
解决PyCharm IDE环境下,执行unittest不生成测试报告的问题
2020/09/03 Python
在购买印度民族服饰:Soch
2020/09/15 全球购物
.NET常见笔试题集
2012/12/01 面试题
会计演讲稿范文
2014/05/23 职场文书
2014年班干部工作总结
2014/11/25 职场文书
证劵公司反洗钱宣传活动总结
2015/05/08 职场文书
《唯一的听众》教学反思
2016/02/18 职场文书
SQL Server中常用截取字符串函数介绍
2022/03/16 SQL Server