深入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实现的一个简单udp广播服务器、客户端
Sep 25 NodeJs
基于NodeJS的前后端分离的思考与实践(三)轻量级的接口配置建模框架
Sep 26 NodeJs
使用Angular和Nodejs、socket.io搭建聊天室及多人聊天室
Aug 21 NodeJs
深入浅析NodeJs并发异步的回调处理
Dec 21 NodeJs
浅谈Nodejs应用主文件index.js
Aug 28 NodeJs
nodejs搭建本地服务器并访问文件的方法
Mar 03 NodeJs
nodejs学习笔记之路由
Mar 27 NodeJs
详解nodejs微信公众号开发——2.自动回复
Apr 10 NodeJs
NodeJs的fs读写删除移动监听
Apr 28 NodeJs
nodejs socket服务端和客户端简单通信功能
Sep 14 NodeJs
Nodejs中crypto模块的安全知识讲解
Jan 03 NodeJs
nodejs中request库使用HTTPS代理的方法
Apr 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
PHP面向对象编程快速入门
2006/10/09 PHP
Ajax+PHP 边学边练 之二 实例
2009/11/24 PHP
php无限分类且支持输出树状图的详细介绍
2013/06/19 PHP
浅析php中jsonp的跨域实例
2013/06/21 PHP
php多用户读写文件冲突的解决办法
2013/11/06 PHP
JQuery从头学起第三讲
2010/07/06 Javascript
非html5实现js版弹球游戏示例代码
2013/09/22 Javascript
不同Jquery版本引发的问题解决
2013/10/14 Javascript
jquery使用hide方法隐藏指定id的元素
2015/03/30 Javascript
jQuery EasyUI 入门必看
2016/06/03 Javascript
JS弹出新窗口被拦截的解决方法
2016/08/09 Javascript
详解bootstrap的modal-remote两种加载方式【强化】
2017/01/27 Javascript
Angular2 http jsonp的实例详解
2017/08/31 Javascript
详解Ubuntu安装angular-cli遇到的坑
2018/09/08 Javascript
更强大的vue ssr实现预取数据的方式
2019/07/19 Javascript
JS实现点击下拉列表文本框中出现对应的网址,点击跳转按钮实现跳转
2019/11/25 Javascript
python使用urllib2提交http post请求的方法
2015/05/26 Python
python中 logging的使用详解
2017/10/25 Python
Python实现爬取百度贴吧帖子所有楼层图片的爬虫示例
2018/04/26 Python
解决Pyinstaller 打包exe文件 取消dos窗口(黑框框)的问题
2019/06/21 Python
python飞机大战pygame游戏背景设计详解
2019/12/17 Python
Numpy 理解ndarray对象的示例代码
2020/04/03 Python
基于matplotlib中ion()和ioff()的使用详解
2020/06/16 Python
解决Python 函数声明先后顺序出现的问题
2020/09/02 Python
如何利用Python给自己的头像加一个小国旗(小月饼)
2020/10/02 Python
python 实现图片批量压缩的示例
2020/12/18 Python
欧洲有机婴儿食品最大的市场:Organic Baby Food(供美国和加拿大)
2018/03/28 全球购物
贪睡宠物用品:Snoozer Pet Products
2020/02/04 全球购物
日语专业毕业生自荐书
2014/06/18 职场文书
小学优秀教师先进事迹材料
2014/12/16 职场文书
公司租车协议书
2015/01/29 职场文书
2015年护士工作总结范文
2015/03/31 职场文书
卡特教练观后感
2015/06/08 职场文书
2015年暑期社会实践总结
2015/07/13 职场文书
python自动化测试通过日志3分钟定位bug
2021/11/20 Python
Android超详细讲解组件ScrollView的使用
2022/03/31 Java/Android