nodejs中流(stream)的理解

nodejs的fs模块并没有提供一个copy的方法,但我们可以很容易的实现一个,比如:

var source = fs.readFileSync(‘/path/to/source‘, {encoding: ‘utf8‘});
fs.writeFileSync(‘/path/to/dest‘, source);

这种方式是把文件内容全部读入内存,然后再写入文件,对于小型的文本文件,这没有多大问题,比如grunt-file-copy就是这样实现的。但是对于体积较大的二进制文件,比如音频、视频文件,动辄几个GB大小,如果使用这种方法,很容易使内存“爆仓”。理想的方法应该是读一部分,写一部分,不管文件有多大,只要时间允许,总会处理完成,这里就需要用到流的概念。

如上面高大上的图片所示,我们把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(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观察一次完成进度,并把已完成的大小、百分比和复制速度一并写到控制台上,当复制完成时,计算总的耗费时间,效果如图:

我们复制了一集1080p的权利的游戏第四季第7集,大概3.78G大小,由于使用了SSD,可以看到速度还是非常不错的,哈哈哈~
复制完成后,显示总花费时间

结合nodejs的readline, process.argv等模块,我们可以添加覆盖提示、强制覆盖、动态指定文件路径等完整的复制方法,有兴趣的可以实现一下,实现完成,可以

ln -s /path/to/copy.js /usr/local/bin/mycopy

这样就可以使用自己写的mycopy命令替代系统的cp命令

时间: 2024-12-19 10:11:49

nodejs中流(stream)的理解的相关文章

大熊君大话NodeJS之------Stream模块

一,开篇分析 流是一个抽象接口,被 Node 中的很多对象所实现.比如对一个 HTTP 服务器的请求是一个流,stdout 也是一个流.流是可读,可写或兼具两者的. 最早接触Stream是从早期的unix开始的, 数十年的实践证明Stream 思想可以很简单的开发出一些庞大的系统. 在unix里,Stream是通过 "|" 实现的.在node中,作为内置的stream模块,很多核心模块和三方模块都使用到. 和unix一样,node stream主要的操作也是.pipe(),使用者可以使

【转】iOS中流(Stream)的使用

转自:http://southpeak.github.io/blog/2014/07/17/ioszhong-liu-stream-de-shi-yong/ 流提供了一种简单的方式在不同和介质中交换数据,这种交换方式是与设备无关的.流是在通信路径中串行传输的连续的比特位序列.从编码的角度来看,流是单向的,因此流可以是输入流或输出流.除了基于文件的流外,其它形式的流都是不可查找的,这些流的数据一旦消耗完后,就无法从流对象中再次获取. 在Cocoa中包含三个与流相关的类:NSStream.NSInp

【前端知识体系-NodeJS相关】对NodeJS模块机制的理解

1. CommonJS模块规范 1.1 模块引用 var math = require('math'); 1.2 模块定义 [!NOTE] 上下文提供exports对象用于导出当前模块的方法和变量,并且他是唯一的导出出口 exports实际上是module.exports,而module.exports就是以一个暴露给外部的对象. exports.some就是给这个对象上添加属性 直接使用 module.exports = {...} 则可以让外部直接获取到这个对象,相当与为exports换了一

好用的 HTTP模块SuperAgent

SuperAgent 最近在写爬虫,看了下node里面有啥关于ajax的模块,发现superagent这个模块灰常的好用.好东西要和大家分享,话不多说,开始吧- 什么是SuperAgent superagent它是一个强大并且可读性很好的轻量级ajaxAPI,是一个关于HTTP方面的一个库,而且它可以将链式写法玩的出神入化. var superagent = require('superagent'); superagent .post('/api') .send({ 'key': 'value

前端面试个人总结

# 面试题总结 ### 基础部分 1. 什么是HTML? 答: - [ ] > ?      HTML并不是真正的的程序语言,他是一种 标 记 语 言 ,用来结构化和含义化你想要放 > 在web 网站上的那些内容.它由一系列的元素(elements)所组成,这些元素可以用来 > 封装你的内容中担任不同工作的各部分和各个角色. 2. 什么是CSS? 答: - [ ] > ?      就像 HTML,CSS 也不是真正的编程语言.它是样式表语言,也就是说,它允许你有 > 选择性

理解互联网域名请求实现过程,以及Nodejs的http请求小谈

前提:在学习开发互联网网站程序前,需要了解知道一个客户端请求,如何能展现成一个炫丽的网页的. 一.域名请求实现 这幅图足以说明一个域名请求的过程了吧 二.服务器端的处理(Nodejs示例) 直接上nodejs代码 1 var http = require('http'); 2 3 http.createServer(function(req, res) { 4 if (req.method === 'GET') { 5 var html; 6 switch (req.url) { 7 case

分针网——每日分享:nodejs导出excel实战

本文转载:http://www.f-z.cn/id/268 我们都知道nodejs的内存由于v8内存分配机制的原因十分有限 64位系统也只能占1.4G左右, 因此当我们要生成或者读取大文件的时候内存的吃紧会给我们造成极大的困扰, 遇到这样的情况Node给了我们一个很好的解决方法 stream 简单的了解一下流 流是数据的集合 -- 就像数组或字符串一样.区别在于流中的数据可能不会立刻就全部可用,并且你无需一次性地把这些数据全部放入内存.这使得流在操作大量数据或是数据从外部来源逐段发送过来的时候变

Java序列化(Serialization)的理解

1.什么是序列化 Java是面向对象的编程语言,有时需要保存对象,并在下次使用时可以顺利还原该对象.由于这种需求很常见,所以Java API对此提供了支持,添加相关程序代码到标准类库中,并将保存和还原的过程称之为"对象序列化". Java SE7 文档中将与对象序列化的相关内容做了详细表述,将其称为: "Java对象序列化规范"  Java Object Serialization Specification,网址为: http://docs.oracle.com/

初探nodeJS

node概要 对nodeJS早有耳闻,但是一直迟迟没有对它下手,哈哈哈,今儿咱就来初探一下它. nodeJS是个啥东东? nodeJS,我的理解就是可以运行在后端的JavaScript. 为什么它能够在后端运行呢? 这就得归功于V8引擎(V8是Google Chrome浏览器的JavaScript引擎),通过对高性能V8引擎的封装,并通过一系列优化的API类库,使其就能够在后端运行了. 并且node有两大特点: 1.基于事件驱动: 2.无阻塞. 从而nodeJS非常适合处理并发请求. 大家都知道