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 相关文章推荐
在textarea中屏蔽js的某个function的javascript代码
Apr 20 Javascript
JavaScript 面向对象的 私有成员和公开成员
May 13 Javascript
jQuery EasyUI API 中文文档 - Documentation 文档
Sep 29 Javascript
页面调用单个swf文件,嵌套出多个方法。
Nov 21 Javascript
JavaScript中数组对象的那些自带方法介绍
Mar 12 Javascript
jquery实现弹出div,始终显示在屏幕正中间的简单实例
Mar 08 Javascript
Javascript控制div属性动态变化实例分析
Oct 08 Javascript
jQuery加载及解析XML文件的方法实例分析
Jan 22 Javascript
javaScript中的空值和假值
Dec 18 Javascript
Angular2学习笔记之数据绑定的示例代码
Jan 03 Javascript
关于JavaScript中高阶函数的魅力详解
Sep 07 Javascript
深入分析element ScrollBar滚动组件源码
Jan 22 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
Apache2 httpd.conf 中文版
2006/12/06 PHP
php 启动时报错的简单解决方法
2014/01/27 PHP
浅谈php中curl、fsockopen的应用
2016/12/10 PHP
Yii2框架中使用PHPExcel导出Excel文件的示例
2017/08/09 PHP
Js之软键盘实现(js源码)
2007/01/30 Javascript
比较简单的一个符合web标准的JS调用flash方法
2007/11/29 Javascript
ASP.NET jQuery 实例14 在ASP.NET form中校验时间范围
2012/02/03 Javascript
javascript模拟map输出与去除重复项的方法
2015/02/09 Javascript
javascript字符串与数组转换汇总
2015/05/26 Javascript
jQuery实现图片上传和裁剪插件Croppie
2015/11/29 Javascript
一个用jquery写的判断div滚动条到底部的方法【推荐】
2016/04/29 Javascript
node-http-proxy修改响应结果实例代码
2016/06/06 Javascript
javascript 中的console.log和弹出窗口alert
2016/08/30 Javascript
浅谈jquery中next与siblings的区别
2016/10/27 Javascript
Javascript 正则表达式校验数字的简单实例
2016/11/02 Javascript
浅析Visual Studio Code断点调试Vue
2018/02/27 Javascript
在vue中使用css modules替代scroped的方法
2018/03/10 Javascript
详解angular应用容器化部署
2018/08/14 Javascript
基于axios 的responseType类型的设置方法
2019/10/29 Javascript
js实现简单的点名器随机色实例代码
2020/09/20 Javascript
学习python之编写简单简单连接数据库并执行查询操作
2016/02/27 Python
Pyinstaller将py打包成exe的实例
2018/03/31 Python
python验证身份证信息实例代码
2019/05/06 Python
在python中,使用scatter绘制散点图的实例
2019/07/03 Python
python实现文件批量编码转换及注意事项
2019/10/14 Python
django实现类似触发器的功能
2019/11/15 Python
django日志默认打印request请求信息的方法示例
2020/05/17 Python
Python 字典中的所有方法及用法
2020/06/10 Python
Python使用Opencv实现边缘检测以及轮廓检测的实现
2020/12/31 Python
推荐10个HTML5响应式框架
2016/02/25 HTML / CSS
奥地利领先的在线药房:SHOP APOTHEKE
2019/10/07 全球购物
教师实习自我鉴定
2013/12/13 职场文书
2014幼儿园卫生保健工作总结
2014/12/05 职场文书
文艺演出主持词
2015/07/01 职场文书
小学体育教学随笔
2015/08/14 职场文书
党章党规党纪学习心得体会
2016/01/14 职场文书