深入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 sublime text 3安装与配置
Jun 19 NodeJs
详解NodeJs支付宝移动支付签名及验签
Jan 06 NodeJs
nodejs+express实现文件上传下载管理网站
Mar 15 NodeJs
nodejs中使用HTTP分块响应和定时器示例代码
Mar 19 NodeJs
nodejs个人博客开发第三步 载入页面
Apr 12 NodeJs
使用nodejs爬取前程无忧前端技能排行
May 06 NodeJs
详解nodeJS之二进制buffer对象
Jun 03 NodeJs
在Debian(Raspberry Pi)树莓派上安装NodeJS的教程详解
Sep 19 NodeJs
详解NODEJS的http实现
Jan 04 NodeJs
nodejs基础之常用工具模块util用法分析
Dec 26 NodeJs
nodejs搭建本地服务器并访问文件操作示例
May 11 NodeJs
详解nodejs内置模块
May 06 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
Sony CFR 320 修复改造
2020/03/14 无线电
NT IIS下用ODBC连接数据库
2006/10/09 PHP
C# WinForm中实现快捷键自定义设置实例
2015/01/23 PHP
laravel多条件查询方法(and,or嵌套查询)
2019/10/09 PHP
jQuery实现 注册时选择阅读条款 左右移动
2013/04/11 Javascript
js图片自动切换效果处理代码
2013/05/07 Javascript
js控制表单操作的常用代码小结
2013/08/15 Javascript
利用JS实现数字增长
2016/07/28 Javascript
深入理解javascript中concat方法
2016/12/12 Javascript
JavaScript实现全选取消效果
2017/12/14 Javascript
ajax与jsonp的区别及用法
2018/10/16 Javascript
Vue+webpack实现懒加载过程解析
2020/02/17 Javascript
微信小程序webSocket的使用方法
2020/02/20 Javascript
JavaScript ECMA-262-3 深入解析(一):执行上下文实例分析
2020/04/25 Javascript
浅谈vue 组件中的setInterval方法和window的不同
2020/07/30 Javascript
vue实现验证用户名是否可用
2021/01/20 Vue.js
[36:33]完美世界DOTA2联赛循环赛 Matador vs Forest 第一场 11.06
2020/11/06 DOTA
Python 冒泡,选择,插入排序使用实例
2015/02/05 Python
探索Python3.4中新引入的asyncio模块
2015/04/08 Python
Python提取Linux内核源代码的目录结构实现方法
2016/06/24 Python
python通过elixir包操作mysql数据库实例代码
2018/01/31 Python
Python-OpenCV基本操作方法详解
2018/04/02 Python
Python自动生成代码 使用tkinter图形化操作并生成代码框架
2019/09/18 Python
opencv+python实现均值滤波
2020/02/19 Python
python 贪心算法的实现
2020/09/18 Python
任意一块网页内容实现“活”的背景(目前火狐浏览器专有)
2014/05/07 HTML / CSS
幼儿园教师个人反思
2014/01/30 职场文书
2014年元旦促销活动方案
2014/02/22 职场文书
有趣的广告词
2014/03/18 职场文书
听课评语大全
2014/04/30 职场文书
小学节能减排倡议书
2014/05/15 职场文书
市场策划求职信
2014/08/07 职场文书
个人师德师风自我剖析材料
2014/09/29 职场文书
专题组织生活会发言材料
2014/10/17 职场文书
Django路由层如何获取正确的url
2021/07/15 Python
MySQL 数据 data 基本操作
2022/05/04 MySQL