NodeJS 中Stream 的基本使用

在 NodeJS 中,我们对文件的操作需要依赖核心模块 fs , fs 中有很基本 API 可以帮助我们读写占用内存较小的文件,如果是大文件或内存不确定也可以通过 open 、 read 、 write 、 close 等方法对文件进行操作,但是这样操作文件每一个步骤都要关心,非常繁琐, fs 中提供了可读流和可写流,让我们通过流来操作文件,方便我们对文件的读取和写入。
可读流

1、createReadStream 创建可读流

createReadStream 方法有两个参数,第一个参数是读取文件的路径,第二个参数为 options 选项,其中有八个参数:

r
null
null
0o666
true
64 * 1024

createReadStream 的返回值为 fs.ReadStream 对象,读取文件的数据在不指定 encoding 时,默认为 Buffer。

let fs = require("fs");
// 创建可读流,读取 1.txt 文件
let rs = fs.creatReadStream("1.txt", {
 start: 0,
 end: 3,
 highWaterMark: 2
});

在创建可读流后默认是不会读取文件内容的,读取文件时,可读流有两种状态,暂停状态和流动状态。

注意:本篇的可写流为流动模式,流动模式中有暂停状态和流动状态,而不是暂停模式,暂停模式是另一种可读流 readable 。

2、流动状态

流动状态的意思是,一旦开始读取文件,会按照 highWaterMark 的值一次一次读取,直到读完为止,就像一个打开的水龙头,水不断的流出,直到流干,需要通过监听 data 事件触发。

假如现在 1.txt 文件中的内容为 0~9 十个数字,我们现在创建可读流并用流动状态读取。

let fs = require("fs");
let rs = fs.createReadStream("1.txt", {
 start: 0,
 end: 3,
 highWaterMark: 2
});
// 读取文件
rs.on("data", data => {
 console.log(data);
});
// 监听读取结束
rs.on("end", () => {
 console.log("读完了");
});
// <Buffer 30 31>
// <Buffer 32 33>
// 读完了

在上面代码中,返回的 rs 对象监听了两个事件:

data:每次读取 highWaterMark 个字节,触发一次 data 事件,直到读取完成,回调的参数为每次读取的 Buffer;

end:当读取完成时触发并执行回调函数。

我们希望最后读到的结果是完整的,所以我们需要把每一次读到的结果在 data 事件触发时进行拼接,以前我们可能使用下面这种方式。

let fs = require("fs");
let rs = fs.createReadStream("1.txt", {
 start: 0,
 end: 3,
 highWaterMark: 2
});
let str = "";
rs.on("data", data => {
 str += data;
});
rs.on("end", () => {
 console.log(str);
});
// 0123

在上面代码中如果读取的文件内容是中文,每次读取的 highWaterMark 为两个字节,不能组成一个完整的汉字,在每次读取时进行 += 操作会默认调用 toString 方法,这样会导致最后读取的结果是乱码。

在以后通过流操作文件时,大部分情况下都是在操作 Buffer,所以应该用下面这种方式来获取最后读取到的结果。

let fs = require("fs");
let rs = fs.createReadStream("1.txt", {
 start: 0,
 end: 3,
 highWaterMark: 2
});
// 存储每次读取回来的 Buffer
let bufArr = [];
rs.on("data", data => {
 bufArr.push(data);
});
rs.on("end", () => {
 console.log(Buffer.concat(bufArr).toString());
});
// 0123

3、暂停状态

在流动状态中,一旦开始读取文件,会不断的触发 data 事件,直到读完,暂停状态是我们每读取一次就直接暂停,不再继续读取,即不再触发 data 事件,除非我们主动控制继续读取,就像水龙头打开放水一次后马上关上水龙头,下次使用时再打开。

类似于开关水龙头的动作,也就是暂停和恢复读取的动作,在可读流返回的 rs 对象上有两个对应的方法, pause 和 resume 。

在下面的场景中我们把创建可读流的结尾位置更改成 9 ,在每次读两个字节并暂停一秒后恢复读取,直到读完 0~9 十个数字。

let fs = require("fs");
let rs = fs.createReadStream("1.txt", {
 start: 0,
 end: 9,
 hithWaterMark: 2
});
let bufArr = [];
rs.on("data", data => {
 bufArr.push(data);
 rs.pause(); // 暂停读取
 console.log("暂停", new Date());
 setTimeout(() => {
  rs.resume(); // 恢复读取
 }, 1000)
});
rs.on("end", () => {
 console.log(Buffer.concat(bufArr).toString());
});
// 暂停 2018-07-03T23:52:52.436Z
// 暂停 2018-07-03T23:52:53.439Z
// 暂停 2018-07-03T23:52:54.440Z
// 暂停 2018-07-03T23:52:55.442Z
// 暂停 2018-07-03T23:52:56.443Z
// 0123456789

4、错误监听

在通过可读流读取文件时都是异步读取,在异步读取中如果遇到错误也可以通过异步监听到,可读流返回值 rs 对象可以通过 error 事件来监听错误,在读取文件出错时触发回调函数,回调函数参数为 err ,即错误对象。

let fs = require("fs");
// 读取一个不存在的文件
let rs = fs.createReadStream("xxx.js", {
 highWarterMark: 2
});
let bufArr = [];
rs.on("data", data => {
 bufArr.push(data);
});
rs.on("err", err => {
 console.log(err);
});
rs.on("end", () => {
 console.log(Buffer.concat(bufArr).toString());
});
// { Error: ENOENT: no such file or directory, open '......xxx.js' ......}

5、打开和关闭文件的监听

流的适用性非常广,不只是文件读写,也可以用在 http 中数据的请求和响应上,但是在针对文件读取返回的 rs 上有两个专有的事件用来监听文件的打开与关闭。

open 事件用来监听文件的打开,回调函数在打开文件后执行, close 事件用来监听文件的关闭,如果创建的可读流的 autoClose 为 true ,在自动关闭文件时触发,回调函数在关闭文件后执行。

let fs = require("fs");
let rs = fs.createReadStream("1.txt", {
 start: 0,
 end: 3,
 highWaterMark: 2
});
rs.on("open", () => {
 console.log("open");
});
rs.on("close", () => {
 console.log("close");
});
// open

在上面代码我们看出只要创建了可读流就会打开文件触发 open 事件,因为默认为暂停状态,没有对文件进行读取,所以不会关闭文件,即不会触发 close 事件。

let fs = require("fs");
let rs = fs.createReadStream("1.txt", {
 start: 0,
 end: 3,
 hithWaterMark: 2
});
rs.on("open", () => {
 console.log("open");
});
rs.on("data", data => {
 console.log(data);
});
rs.on("end", () => {
 console.log("end");
});
rs.on("close", () => {
 console.log("close");
});
// open
// <Buffer 30 31>
// <Buffer 32 33>
// end
// close

从上面例子执行的打印结果可以看出只有开始读取文件并读完后,才会关闭文件并触发 close 事件, end 事件的触发要早于 close 。

可写流

1、createWriteStream 创建可写流

createWriteStream 方法有两个参数,第一个参数是读取文件的路径,第二个参数为 options 选项,其中有七个参数:

w
utf8
null
0o666
true
16 * 1024
createWriteStream 返回值为 fs.WriteStream 对象,第一次写入时会真的写入文件中,继续写入,会写入到缓存中。
let fs = require("fs");
// 创建可写流,写入 2.txt 文件
let ws = fs.createWriteStream("2.txt", {
 start: 0,
 highWaterMark: 3
});

2、可写流的 write 方法

在可写流中将内容写入文件需要使用 ws 的 write 方法,参数为写入的内容,返回值是一个布尔值,代表 highWaterMark 的值是否足够当前的写入,如果足够,返回 true ,否则返回 false ,换种说法就是写入内容的长度是否超出了 highWaterMark ,超出返回 false 。

let fs = require("fs");
let ws = fs.createWriteSteam("2.txt", {
 start: 0,
 highWaterMark: 3
});
let flag1 = ws.write("1");
console.log(flag1);
let flag2 = ws.write("2");
console.log(flag2);
let flag3 = ws.write("3");
console.log(flag3);
// true
// true
// false

写入不存在的文件时会自动创建文件,如果 start 的值不是 0 ,在写入不存在的文件时默认找不到写入的位置。

3、可写流的 drain 事件

drain 意为 “吸干”,当前写入的内容已经大于等于了 highWaterMark ,会触发 drain 事件,当内容全部从缓存写入文件后,会执行回调函数。

let fs = require("fs");
let ws = fs.createWriteStream("2.txt", {
 start: 0,
 highWaterMark: 3
});
let flag1 = ws.write("1");
console.log(flag1);
let flag2 = ws.write("2");
console.log(flag2);
let flag3 = ws.write("3");
console.log(flag3);
ws.on("drain", () => {
 console.log("吸干");
});
// true
// true
// false

4、可写流的 end 方法

end 方法传入的参数为最后写入的内容, end 会将缓存未写入的内容清空写入文件,并关闭文件。

let fs = require("fs");
let ws = fs.createWriteStream("2.txt", {
 start: 0,
 highWaterMark: 3
});
let flag1 = ws.write("1");
console.log(flag1);
let flag2 = ws.write("2");
console.log(flag2);
let flag3 = ws.write("3");
console.log(flag3);
ws.on("drain", () => {
 console.log("吸干");
});
ws.end("写完了");
// true
// true
// false

在调用 end 方法后,即使再次写入的值超出了 highWaterMark 也不会再触发 drain 事件了,此时打开 2.txt 后发现文件中的内容为 "123写完了"。

let fs = require("fs");
let ws = fs.createWriteStream("2.txt", {
 start: 0,
 highWaterMark: 3
});
ws.write("1");
ws.end("写完了");
ws.write("2");
// Error [ERR_STREAM_WRITE_AFTER_END]: write after end...

在调用 end 方法后,不可以再调用 write 方法写入,否则会报一个很常见的错误 write after end ,文件原有内容会被清空,而且不会被写入新内容。

可写流与可读流混合使用

可写流和可读流一般配合来使用,读来的内容如果超出了可写流的 highWaterMark ,则调用可读流的 pause 暂停读取,等待内存中的内容写入文件,未写入的内容小于 highWaterMark 时,调用可写流的 resume 恢复读取,创建可写流返回值的 rs 上的 pipe 方法是专门用来连接可读流和可写流的,可以将一个文件读来的内容通过流写到另一个文件中。

let fs = require("pipe");
// 创建可读流和可写流
let rs = fs.createReadStream("1.txt", {
 highWaterMark: 3
});
let ws = fs.createWriteStream("2.txt", {
 highWaterMark: 2
});
// 将 1.txt 的内容通过流写入 2.txt 中
rs.pipe(ws);

通过上面的这种类似于管道的方式,将一个流从一个文件输送到了另一个文件中,而且会根据读流和写流的 highWaterMark 自由的控制写入的 “节奏”,不用担心内存的消耗。

总结

以上所述是小编给大家介绍的NodeJS 中Stream 的基本使用,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 深入nodejs中流(stream)的理解

    nodejs的fs模块并没有提供一个copy的方法,但我们可以很容易的实现一个,比如: var source = fs.readFileSync('/path/to/source', {encoding: 'utf8'}); fs.writeFileSync('/path/to/dest', source); 这种方式是把文件内容全部读入内存,然后再写入文件,对于小型的文本文件,这没有多大问题,比如grunt-file-copy就是这样实现的.但是对于体积较大的二进制文件,比如音频.视频文件,动

  • Nodejs Stream 数据流使用手册

    1.介绍 本文介绍了使用 node.js streams 开发程序的基本方法. <code class="hljs mizar">"We should have some ways of connecting programs like garden hose--screw in another segment when it becomes necessary to massage data in another way. This is the way of

  • 理解nodejs的stream和pipe机制的原理和实现

    前言 前几天别人请教我关于pipe的问题,我发现我虽然用了nodejs很久,但是由于每次用的不多所以经常回避stream的使用,导致一直不熟,现在重新学习整理一下相关知识. 通过nodeschool学习stream nodeschool有一个stream-adventure教程教导stream的使用,很简单 简单stream进行pipe 首先,我们可以通过管道将输入定位到输出,输入输出可以是控制台或者文件流或者http请求,比如 process.stdin.pipe(process.stdout

  • 详解nodeJs文件系统(fs)与流(stream)

    一.简介 本文将介绍node.js文件系统(fs)和流(stream)的一些API已经参数使用情况. 二.目录 文件系统将介绍以下方法: 1.fs.readFile 2.fs.writeFile 3.fs.open 4.fs.read 5.fs.stat 6.fs.close 7.fs.mkdir 8.fs.rmdir 9.fs.readdir 10.fs.unlink stream流的四种类型readable,writable,duplex,transform以及stream对象的事件. 三.

  • Nodejs学习笔记之Stream模块

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

  • 详解Nodejs 通过 fs.createWriteStream 保存文件

    工作中难免会遇到处理大文件的时候,有这种stream的处理方式,就不需要一次处理太大的文件,从而导致内存不够用,或者内存占用太多. fs.createWriteStream 似乎不会自己创建不存在的文件夹,所以在使用之前需要注意,保存文件的文件夹一定要提前创建. const path = '/xxxxxx/ddd/'; if (!fs.existsSync(path)) { fs.mkdirSync(path); } 创建完文件夹,我们就可以进行文件添加操作了.我们希望在使用文件添加操作的时候是

  • NodeJS 中Stream 的基本使用

    在 NodeJS 中,我们对文件的操作需要依赖核心模块 fs , fs 中有很基本 API 可以帮助我们读写占用内存较小的文件,如果是大文件或内存不确定也可以通过 open . read . write . close 等方法对文件进行操作,但是这样操作文件每一个步骤都要关心,非常繁琐, fs 中提供了可读流和可写流,让我们通过流来操作文件,方便我们对文件的读取和写入. 可读流 1.createReadStream 创建可读流 createReadStream 方法有两个参数,第一个参数是读取文

  • NodeJs中的非阻塞方法介绍

    首先我们利用NodeJs先构建一个基本的服务器. index.js 复制代码 代码如下: var requestHandler = require("./requestHandler"); var server = require("./server"); var route = { "/hello": requestHandler.hello, "/upload": requestHandler.upload }; serv

  • nodejs中使用archive压缩文件的实现代码

    前言 archive是一款在nodejs中可以实现跨平台打包的工具 可以将文件压缩为zip或rar格式 是一个比较好用的第三方模块 install npm install archiver --save archive github地址:https://github.com/archiverjs/node-archiver Quick Start // require modules var fs = require('fs'); var archiver = require('archiver

  • 如何在Nodejs中使用模块fs文件系统

    概述 node 的fs文档密密麻麻的 api 非常多,毕竟全面支持对文件系统的操作.文档组织的很好,操作基本分为文件操作.目录操作.文件信息.流这个大方面,编程方式也支持同步.异步和 Promise. 本文记录了几个文档中没详细描写的问题,可以更好地串联fs文档思路: 文件描述符 同步.异步与 Promise 目录与目录项 文件信息 stream 文件描述符 文件描述符是一个非负整数.它是一个索引值,操作系统可以根据它来找到对应的文件. 在 fs 的很多底层 api 中,需要用到文件描述符.在文

  • 解决nodejs中使用http请求返回值为html时乱码的问题

    今天用nodejs进行http请求时返回的数据是一个html文件,然后我还是按照以前解析json数据的方法.果不其然报错了:SyntaxError: Unexpected token  in JSON at position 0 没办法,只好换一种方法,将接受到的Buffer对象toString,然后打印出来发现是乱码. 第一感觉是编码问题,google一下然后看官方文档,总结三种方法: 1.toString 加编码格式作为参数. 2.使用iconv-lite 改变编码. 3.使用cheerio

  • nodejs中art-template模板语法的引入及冲突解决方案

    使用Webstorm创建nodejs express应用时,默认使用的是jade或者ejs模板,对于不习惯这两种模板语法的人来说确实不是很方便.没关系,这里我们使用art-template模板引擎,使用后可以直接使用html模板: 1.安装art-template npm install art-template 2.修改app.js文件,添加如下代码: // view engine setup app.set('views', path.join(__dirname, 'views')); v

  • nodejs中sleep功能实现暂停几秒的方法

    我在网上了解了一下nodejs中sleep功能,搜索了很多关于nodejs中sleep功能介绍,下面我来记录一下,有需要了解的朋友可参考.希望此文章对各位有所帮助. 一 背景 在使用nodejs爬虫的时候,经常会遇到别人的网站对频率的反爬机制,这个时候如果不做处理程序就会挂掉,重新启动也会继续被屏蔽.这个问题怎么解决呢,我的想法就是程序暂停10分钟或者更长的时间,继续爬取. 二 方法 其实使用setTimeout就可以实现,只是nodejs异步已经很金字塔了,再加一层会更恐怖,所以本文使用nod

  • 浅谈nodejs中的类定义和继承的套路

    javascript是一门极其灵活的语言. 灵活到你无法忍受! 我个人喜欢强类型的语言,例如c/c++,c#等. 但是js代表着未来,所以需要学习. js中类定义以及继承有n多种方式,现在来学习一下nodejs类定义以及继承的固定套路. 套路1. 在构造函数(constructor)中总是使用instanceof操作符: function Base() { if (!(this instanceof Base)) { return new Base(); } } 上述代码的含义就是: 如果Bas

  • 浅析Node.js 中 Stream API 的使用

    本文由浅入深给大家介绍node.js stream api,具体详情请看下文吧. 基本介绍 在 Node.js 中,读取文件的方式有两种,一种是用 fs.readFile ,另外一种是利用 fs.createReadStream 来读取. fs.readFile 对于每个 Node.js 使用者来说最熟悉不过了,简单易懂,很好上手.但它的缺点是会先将数据全部读入内存,一旦遇到大文件的时候,这种方式读取的效率就非常低下了. 而 fs.createReadStream 则是通过 Stream 来读取

随机推荐