Node.js Streams文件读写操作详解


Posted in Javascript onJuly 04, 2016

Node.js 天生异步和事件驱动,非常适合处理 I/O 相关的任务。如果你在处理应用中 I/O 相关的操作,你可以利用 Node.js 中的流(stream)。因此,我们先具体看看流,理解一下它们是怎么简化 I/O 操作的吧。 

流是什么
流是 unix 管道,让你可以很容易地从数据源读取数据,然后流向另一个目的地。
简单来说,流不是什么特别的东西,它只是一个实现了一些方法的 EventEmitter 。根据它实现的方法,流可以变成可读流(Readable),可写流(Writable),或者双向流(Duplex,同时可读可写)。
可读流能让你从一个数据源读取数据,而可写流则可以让你往目的地写入数据。
如果你已经用过 Node.js,你很可能已经遇到过流了。
例如,在一个 Node.js 的 HTTP 服务器里面, request 是一个可读流, response 是一个可写流。
你也可能用过 fs 模块,它能帮你处理可读可写流。
 现在让你学一些基础,理解不同类型的流。本文会讨论可读流和可写流,双向流超出了本文的讨论范围,我们不作讨论。
 可读流 (Readable Streams)
 我们可以用可读流从一个数据源中读取数据,这个数据源可以是任何东西,例如系统中的一个文件,内存中的 buffer,甚至是其他流。因为流是 EventEmitter ,它们会用各种事件发送数据。我们会利用这些事件来让流工作。

从流中读取数据
 从流中读取数据最好的方式是监听 data 事件,添加一个回调函数。当有数据流过来的时候,可读流会发送 data 事件,回调函数就会触发。看看下面的代码片段:

var fs = require('fs');
var readableStream = fs.createReadStream('file.txt');
var data = '';

var readableStream.on('data', function(chunk){
 data += chunk;
});

readableStream.on('end', function(){
 console.log(data);
});

fs.createReadStream 会给你一个可读流。
最开始的时候,这个流不是流动态的。当你添加了 data 的事件监听器,加上一个回调函数时,它才会变成流动态的。在这之后,它就会读取一小块数据,然后传到你的回调函数里面。
流的实现者决定了 data 事件的触发频率,例如 HTTP request 会在读取到几 KB 数据的时候触发 data 事件。 当你从一个文件中读取数据的时候,你可能会决定当一行被读完的时候就触发 data 事件。
当没有数据可读的时候 (读到文件尾部时),流就会发送 end 事件。在上面的例子中,我们监听了这个事件,当读完文件的时候,就把数据打印出来。
还有另一种读取流的方式,你只要在读到文件尾部前不断调用流实例中的 read() 方法就可以了。

var fs = require('fs');
var readableStream = fs.createReadStream('file.txt');
var data = '';
var chunk;

readableStream.on('readable', function(){
 while ((chunk = readableStream.read()) != null) {
 data += chunk;
 }
});

readableStream.on('end', function(){
 console.log(data);
});

read() 方法会从内部 buffer 中读取数据,当没有数据可读的时候,它会返回 null 。
因此,在 while 循环中我们检查 read() 是不是返回 null ,当它返回 null 的时候,就终止循环。
需要注意的是,当我们可以从流中读取数据的时候, readable 事件就会触发。
设置编码
默认情况下,你从流中读取到的是 Buffer 对象。如果你要读取的是字符串的话,这并不适合你。因此,你可以像下面的例子那样通过调用 Readable.setEncoding() 来设置流的编码:

var fs = require('fs');
var readableStream = fs.createReadStream('file.txt');
var data = '';

readableStream.setEncoding('utf8');

readableStream.on('data', function(chunk){
 data += chunk;
});

readableStream.on('end', function(){
 console.log(data);
});

上面的例子中,我们把流的编码设置成 utf8 ,数据就会被解析成 utf8 ,回调函数中的 chunk 就会是字符串了。
管道 (Piping)
管道是一个很棒的机制,你不需要自己管理流的状态就可以从数据源中读取数据,然后写入到目的地中。我们先看看下面的例子:

var fs = require('fs');
var readableStream = fs.createReadStream('file1.txt');
var writableStream = fs.createWriteStream('file2.txt');

readableStream.pipe(writableStream);

上面的例子利用 pipe() 方法把 file1 的内容写到 file2 中。因为 pipe() 会帮你管理数据流,你不需要担心数据流的速度。这让 pipe() 变得非常简洁易用。
需要注意的是, pipe() 会返回目的地的流,因此你可以很轻易让多个流链接起来!
链接 (Chaining)
假设有一个归档文件,你想要解压它。有很多方式可以完成这个任务。但最简洁的方式是利用管道和链接:

var fs = require('fs');
var zlib = require('zlib');

fs.createReadStream('input.txt.gz')
 .pipe(zlib.createGunzip())
 .pipe(fs.createWriteStream('output.txt'));

首先,我们通过 input.txt.gz 创建了一个可读流,然后让它流 zlib.createGunzip() 流,它会解压内容。最后,我们添加一个可写流把解压后的内容写到另一个文件中。
其他方法
 我们已经讨论了一些可读流中重要的概念了,这里还有一些你需要知道的方法:
 1.Readable.pause() ? 这个方法会暂停流的流动。换句话说就是它不会再触发 data 事件。
 2.Readable.resume() ? 这个方法和上面的相反,会让暂停流恢复流动。
 3.Readable.unpipe() ? 这个方法会把目的地移除。如果有参数传入,它会让可读流停止刘翔某个特定的目的地,否则,它会移除所有目的地。 

可写流 (Writable Streams)
可写流让你把数据写入目的地。就像可读流那样,这些也是 EventEmitter ,它们也会触发不同的事件。我们来看看可写流中会触发的事件和方法吧。
写入流 
要把数据写如到可写流中,你需要在可写流实例中调用 write() 方法,看看下面的例子:

var fs = require('fs');
var readableStream = fs.createReadStream('file1.txt');
var writableStream = fs.createWriteStream('file2.txt');

readableStream.setEncoding('utf8');

readableStream.on('data', function(chunk){
 writableStream.write('chunk');
});

上面的代码非常简单,它只是从输入流中读取数据,然后用 write() 写入到目的地中。
 这个方法返回一个布尔值来表示写入是否成功。如果返回的是 true 那表示写入成功,你可以继续写入更多的数据。 如果是 false ,那意味着发生了什么错误,你现在不能继续写入了。可写流会触发一个 drain 事件来告诉你你可以继续写入数据。
 写完数据后
 当你不需要在写入数据的时候,你可以调用 end() 方法来告诉流你已经完成写入了。假设 res 是一个 HTTP response 对象,你通常会发送响应给浏览器:
res.write('Some Data!!');
res.end();
当 end() 被调用时,所有数据会被写入,然后流会触发一个 finish 事件。注意在调用 end() 之后,你就不能再往可写流中写入数据了。例如下面的代码就会报错:
res.write('Some Data!!');
res.end();
res.write('Trying to write again'); //Error !
这里有一些和可写流相关的重要事件:
 1.error ? 在写入或链接发生错误时触发
 2.pipe ? 当可读流链接到可写流时,这个事件会触发
 3.unpipe ? 在可读流调用 unpipe 时会触发

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jquery实现select选择框内容左右移动代码分享
Nov 21 Javascript
jQuery mobile类库使用时加载导航历史的方法简介
Dec 04 Javascript
Node.js的MongoDB驱动Mongoose基本使用教程
Mar 01 Javascript
Bootstrap中的表单验证插件bootstrapValidator使用方法整理(推荐)
Jun 21 Javascript
Bootstrap和Java分页实例第二篇
Dec 23 Javascript
js调用刷新界面的几种方式
May 03 Javascript
详解vue中computed 和 watch的异同
Jun 30 Javascript
Element-UI踩坑之Pagination组件的使用
Oct 29 Javascript
用node撸一个监测复联4开售短信提醒的实现代码
Apr 10 Javascript
vue通过video.js解决m3u8视频播放格式的方法
Jul 30 Javascript
分享一款超好用的JavaScript 打包压缩工具
Apr 26 Javascript
vue项目查看vue版本及cli版本的实现方式
Oct 24 Javascript
jQuery文字提示与图片提示效果实现方法
Jul 04 #Javascript
jQuery实现的导航下拉菜单效果
Jul 04 #Javascript
表单中单选框添加选项和移除选项
Jul 04 #Javascript
jQuery实现简单倒计时功能的方法
Jul 04 #Javascript
jquery设置表单元素为不可用的简单代码
Jul 04 #Javascript
JavaScript 节流函数 Throttle 详解
Jul 04 #Javascript
jQuery实现订单提交页发送短信功能前端处理方法
Jul 04 #Javascript
You might like
一个XML格式数据转换为图表的例子
2010/02/09 Javascript
jquery ui resizable bug解决方法
2010/10/26 Javascript
Pro JavaScript Techniques学习笔记
2010/12/28 Javascript
js Event对象的5种坐标
2011/09/12 Javascript
方便实用的jQuery checkbox复选框全选功能简单实例
2013/10/09 Javascript
调用HttpHanlder的几种返回方式小结
2013/12/20 Javascript
js插件方式打开pdf文件(浏览器pdf插件分享)
2013/12/20 Javascript
使用JQUERY进行后台页面布局控制DIV实现左右式
2014/01/07 Javascript
jquery如何把数组变为字符串传到服务端并处理
2014/04/30 Javascript
详解Angularjs filter过滤器
2016/02/06 Javascript
WebPack基础知识详解
2017/01/16 Javascript
AngularJS实现表单元素值绑定操作示例
2017/10/11 Javascript
如何使用puppet替换文件中的string
2018/12/06 Javascript
详解使用Nuxt.js快速搭建服务端渲染(SSR)应用
2019/03/13 Javascript
JavaScript this在函数中的指向及实例详解
2019/10/14 Javascript
Vue 实现对quill-editor组件中的工具栏添加title
2020/08/03 Javascript
Python模拟登录验证码(代码简单)
2016/02/06 Python
python 删除大文件中的某一行(最有效率的方法)
2017/08/19 Python
Python实现字符串匹配算法代码示例
2017/12/05 Python
Tensorflow卷积神经网络实例进阶
2018/05/24 Python
selenium+python设置爬虫代理IP的方法
2018/11/29 Python
Python 一句话生成字母表的方法
2019/01/02 Python
Python字符串查找基本操作代码案例
2020/10/27 Python
梵蒂冈和罗马卡:Omnia Card Pass
2018/02/10 全球购物
自考毕业生自我鉴定
2013/11/04 职场文书
荷叶圆圆教学反思
2014/02/01 职场文书
培训协议书范本
2014/04/22 职场文书
红头文件任命书范本
2014/06/05 职场文书
乡镇爱国卫生月活动总结
2014/06/25 职场文书
违反工作规定检讨书范文
2014/12/14 职场文书
2015年度培训工作总结范文
2015/04/02 职场文书
人事任命通知
2015/04/20 职场文书
银行资信证明
2015/06/17 职场文书
团支部组织委员竞选稿
2015/11/21 职场文书
Python绘制地图神器folium的新人入门指南
2021/05/23 Python
Python torch.flatten()函数案例详解
2021/08/30 Python