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 相关文章推荐
JavaScript字符串对象split方法入门实例(用于把字符串分割成数组)
Oct 16 Javascript
Javascript实现获取窗口的大小和位置代码分享
Dec 04 Javascript
JavaScript实现的双向跨域插件分享
Jan 31 Javascript
jquery中radio checked问题
Mar 16 Javascript
JavaScript 正则表达式中global模式的特性
Feb 25 Javascript
浅谈jQuery 选择器和dom操作
Jun 07 Javascript
浅谈javascript:两种注释,声明变量,定义函数
Sep 29 Javascript
一个炫酷的Bootstrap导航菜单
Dec 28 Javascript
使用JS动态显示文本
Sep 09 Javascript
mpvue+vant app搭建微信小程序的方法步骤
Feb 11 Javascript
详解mpvue开发微信小程序基础知识
Sep 23 Javascript
vue-quill-editor 自定义工具栏和自定义图片上传路径操作
Aug 03 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
PHP实现用户认证及管理完全源码
2007/03/11 PHP
PHP重载基础知识回顾
2020/09/10 PHP
js中访问html中iframe的文档对象的代码[IE6,IE7,IE8,FF]
2011/01/08 Javascript
JavaScript的parseInt 取整使用
2011/05/09 Javascript
读jQuery之十二 删除事件核心方法
2011/07/31 Javascript
纯Javascript实现Windows 8 Metro风格实现
2013/10/15 Javascript
js闭包实例汇总
2014/11/09 Javascript
基于jquery实现复选框全选,反选,全不选等功能
2015/10/16 Javascript
页面向下滚动ajax获取数据的实现方法(兼容手机)
2016/05/24 Javascript
js的各种排序算法实现(总结)
2016/07/23 Javascript
JavaScript中省略元素对数组长度的影响
2016/10/26 Javascript
浅析JavaScriptSerializer类的序列化与反序列化
2016/11/22 Javascript
你真的了解BOM中的history对象吗
2017/02/13 Javascript
vue源码学习之Object.defineProperty 对数组监听
2018/05/30 Javascript
nodejs中实现修改用户路由功能
2019/05/24 NodeJs
js如何实现元素曝光上报
2019/08/07 Javascript
TypeScript之调用栈的实现
2019/12/31 Javascript
JS通用方法触发点击事件代码实例
2020/02/17 Javascript
javascript+css实现进度条效果
2020/03/25 Javascript
Vue2.0 ES6语法降级ES5的操作
2020/10/30 Javascript
Python装饰器使用实例:验证参数合法性
2015/06/24 Python
Python删除空文件和空文件夹的方法
2015/07/14 Python
Python只用40行代码编写的计算器实例
2017/05/10 Python
python3+opencv3识别图片中的物体并截取的方法
2018/12/05 Python
对python的输出和输出格式详解
2018/12/08 Python
python Matplotlib底图中鼠标滑过显示隐藏内容的实例代码
2019/07/31 Python
python 的numpy库中的mean()函数用法介绍
2020/03/03 Python
mac 上配置Pycharm连接远程服务器并实现使用远程服务器Python解释器的方法
2020/03/19 Python
英国女性时尚精品店:THE DRESSING ROOM
2018/05/23 全球购物
介绍一下常见的木马种类
2014/11/15 面试题
护士自我介绍信
2014/01/13 职场文书
标准大学生职业生涯规划书写作指南
2014/09/18 职场文书
详解TS数字分隔符和更严格的类属性检查
2021/05/06 Javascript
Python基础之变量的相关知识总结
2021/06/23 Python
MySQL创建表操作命令分享
2022/03/25 MySQL
AndroidStudio图片压缩工具ImgCompressPlugin使用实例
2022/08/05 Java/Android