深入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的10个性能优化技巧
Jul 15 NodeJs
Nodejs学习笔记之Global Objects全局对象
Jan 13 NodeJs
NodeJS整合银联网关支付(DEMO)
Nov 09 NodeJs
解析NodeJs的调试方法
Dec 11 NodeJs
详解nodejs 文本操作模块-fs模块(一)
Dec 22 NodeJs
实例分析nodejs模块xml2js解析xml过程中遇到的坑
Mar 18 NodeJs
详解Nodejs之静态资源处理
Jun 05 NodeJs
详解使用PM2管理nodejs进程
Oct 24 NodeJs
nodejs取得当前执行路径的方法
May 13 NodeJs
Linux Centos7.2下安装nodejs&amp;npm配置全局路径的教程
May 15 NodeJs
nodeJs的安装与npm全局环境变量的配置详解
Jan 06 NodeJs
nodejs中内置模块fs,path常见的用法说明
Nov 07 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
详解:――如何将图片储存在数据库里
2006/12/05 PHP
解析php获取字符串的编码格式的方法(函数)
2013/06/21 PHP
基于jQueryUI和Corethink实现百度的搜索提示功能
2016/11/09 PHP
ECMAScript 基础知识
2007/06/29 Javascript
js中indexof的用法详细解析
2013/12/24 Javascript
JavaScript中的索引数组、关联数组和静态数组、动态数组讲解
2014/11/08 Javascript
javascript关于open.window子页面执行完成后刷新父页面的问题分析
2015/04/27 Javascript
基于Jquery+div+css实现弹出登录窗口(代码超简单)
2015/10/27 Javascript
AngularJS中处理多个promise的方式
2016/02/02 Javascript
AngularJS实现controller控制器间共享数据的方法示例
2017/10/30 Javascript
JavaScript寄生组合式继承实例详解
2018/01/06 Javascript
JS基于设计模式中的单例模式(Singleton)实现封装对数据增删改查功能
2018/02/06 Javascript
JS内部事件机制之单线程原理
2018/07/02 Javascript
jquery 通过ajax请求获取后台数据显示在表格上的方法
2018/08/08 jQuery
vue--点击当前增加class,其他删除class的方法
2018/09/15 Javascript
es6 for循环中let和var区别详解
2020/01/12 Javascript
十分钟教你上手ES2020新特性
2020/02/12 Javascript
vue 使用 vue-pdf 实现pdf在线预览的示例代码
2020/04/26 Javascript
Python运用于数据分析的简单教程
2015/03/27 Python
详解python开发环境搭建
2016/12/16 Python
python使用TensorFlow进行图像处理的方法
2018/02/28 Python
Python cookbook(数据结构与算法)从序列中移除重复项且保持元素间顺序不变的方法
2018/03/13 Python
python3转换code128条形码的方法
2019/04/17 Python
django实现用户注册实例讲解
2019/10/30 Python
6行Python代码实现进度条效果(Progress、tqdm、alive-progress​​​​​​​和PySimpleGUI库)
2020/01/06 Python
Python unittest工作原理和使用过程解析
2020/02/24 Python
基于HTML5陀螺仪实现ofo首页眼睛移动效果的示例
2017/07/31 HTML / CSS
Topshop法国官网:英国快速时尚品牌
2018/04/08 全球购物
毕业生优秀推荐信
2013/11/26 职场文书
语文教学感言
2014/02/06 职场文书
总裁助理岗位职责
2014/02/17 职场文书
学习演讲稿范文
2014/05/10 职场文书
端午节寄语2015
2015/03/23 职场文书
幼儿园教师师德师风承诺书
2015/04/28 职场文书
go语言中GOPATH GOROOT的作用和设置方式
2021/05/05 Golang
警用民用对讲机找不同
2022/02/18 无线电