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

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

flags:标识位,默认为 r;
encoding:字符编码,默认为 null;
fd:文件描述符,默认为 null;
mode:权限位,默认为 0o666;
autoClose:是否自动关闭文件,默认为 true;
start:读取文件的起始位置;
end:读取文件的(包含)结束位置;
highWaterMark:最大读取文件的字节数,默认 64 * 1024。
createReadStream 的返回值为 fs.ReadStream 对象,读取文件的数据在不指定 encoding 时,默认为 Buffer。

创建可读流
const 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 十个数字,我们现在创建可读流并用流动状态读取。

流动状态
const 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 事件触发时进行拼接,以前我们可能使用下面这种方式。

错误拼接数据的方式
const 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,所以应该用下面这种方式来获取最后读取到的结果。

正确拼接数据的方式
const 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 十个数字。

暂停状态
const 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,即错误对象。

错误监听
const 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,在自动关闭文件时触发,回调函数在关闭文件后执行。

打开和关闭可读流的监听
const 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 事件。

暂停状态
const 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 选项,其中有七个参数:

flags:标识位,默认为 w;
encoding:字符编码,默认为 utf8;
fd:文件描述符,默认为 null;
mode:权限位,默认为 0o666;
autoClose:是否自动关闭文件,默认为 true;
start:写入文件的起始位置;
highWaterMark:一个对比写入字节数的标识,默认 16 * 1024。
createWriteStream 返回值为 fs.WriteStream 对象,第一次写入时会真的写入文件中,继续写入,会写入到缓存中。

创建可写流
const fs = require(“fs”);

// 创建可写流,写入 2.txt 文件
let ws = fs.createWriteStream(“2.txt”, {
start: 0,
highWaterMark: 3
});复制代码
2、可写流的 write 方法
在可写流中将内容写入文件需要使用 ws 的 write 方法,参数为写入的内容,返回值是一个布尔值,代表 highWaterMark 的值是否足够当前的写入,如果足够,返回 true,否则返回 false,换种说法就是写入内容的长度是否超出了 highWaterMark,超出返回 false。

write 方法写入
const 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 事件,当内容全部从缓存写入文件后,会执行回调函数。

drain 事件
const 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 会将缓存未写入的内容清空写入文件,并关闭文件。

end 方法
const 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写完了”。

常见报错
const 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 方法是专门用来连接可读流和可写流的,可以将一个文件读来的内容通过流写到另一个文件中。

pipe 方法使用
const fs = require(“fs”);

// 创建可读流和可写流
let rs = fs.createReadStream(“1.txt”, {
highWaterMark: 3
});
let ws = fs.createWriteStream(“2.txt”, {
highWaterMark: 2
});

// 将 1.txt 的内容通过流写入 2.txt 中
rs.pipe(ws);复制代码
通过上面的这种类似于管道的方式,将一个流从一个文件输送到了另一个文件中,而且会根据读流和写流的 highWaterMark 自由的控制写入的 “节奏”,不用担心内存的消耗。

总结
这篇是关于读流和写流的基本用法,在平时的开发当中,大多数的 API 都用不到,只有最后的 pipe 用的最多,无论是在文件的读写还是请求的响应,其他的 API 虽然用的少,但是作为一个合格的程序员一定要有所了解。

NodeJS Stream(可读流、可写流) API解读相关推荐

  1. node 流学习笔记 - 可写流

    可写流 可写流没有会创建,有内容的话会清空 默认情况下一次能写 16 * 1024 缓存区,第一次写入是真的向文件里写入,第二次在写入的时候放入到了缓存区里 写入时候返回一个boolean类型,返回为 ...

  2. node那点事(二) -- Writable streams(可写流)、自定义流

    可写流(Writable Stream) 可写流是对数据写入'目的地'的一种抽象. 可写流的原理其实与可读流类似,当数据过来的时候会写入缓存池,当写入的速度很慢或者写入暂停时候,数据流便会进入到队列池 ...

  3. 一文读懂Java中File类、字节流、字符流、转换流

    一文读懂Java中File类.字节流.字符流.转换流 第一章 递归:File类: 1.1:概述 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建.查找和删除等操作. ...

  4. Stream流、FiLe和IO流、IO流(字节流-拷贝文件_和_字符流-读取文本中的数据写入文本文件中)9-10-11

    package com.streamdemo; import java.util.ArrayList; import java.util.List; /*** 体验Stream流** 创建一个集合,存 ...

  5. Java基础巩固(二)异常,多线程,线程池,IO流,Properties集合,IO工具类,字符流,对象流,Stream,Lambda表达式

    一.异常,多线程 学习目标 : 异常的概述 异常的分类 异常的处理方式 自定义异常 多线程入门 1 异常的概述 1.1 什么是异常? 异常就是程序出现了不正常情况 , 程序在执行过程中 , 数据导致程 ...

  6. .net 流(Stream) - 文件流、内存流、网络流

    一.文件流 FileStream FileStream流继承与Stream类,一个FileStream类的实例实际上代表一个文件流,使用FileStream类可以对文件系统上是文件进行读取.写入.打开 ...

  7. 如何开发一个Feeds流系统——写扩散模式为例

    一.了解Feeds流 在学习如何开发Feeds流应用前,我们需要先了解什么是Feeds流. 1. 什么是Feeds流 Feeds流是一个持续更新并展示给用户的信息流.它将用户主动订阅的若干消息源组合在 ...

  8. 网络语音流隐写分析全流程 (Steganalysis of VoIP Speech Streams)

    欢迎访问我的个人博客:https://hi.junono.com/ AMR隐写数据集地址(Kaggle) 网络语音流隐写分析全流程 隐写分析流程介绍: 基本知识 **基于网络语音(VoIP)流的隐写术 ...

  9. c#中字节数组byte[]、图片image、流stream,字符串string、内存流MemoryStream、文件file,之间的转换

    字节数组byte[]与图片image之间的转化 字节数组转换成图片 public static Image byte2img(byte[] buffer) {MemoryStream ms = new ...

最新文章

  1. 商汤提基于贪心超网络的One-Shot NAS,达到最新SOTA | CVPR 2020
  2. php和python哪个工资高-前端,java,php,python工程师哪个最缺 知乎
  3. 团队-团队编程项目作业名称-需求分析;
  4. python3爬虫初探(三)之正则表达式
  5. pdf转ppt_好用的pdf转ppt(一个神站+一个神器)
  6. 拓端tecdat|R平方/相关性取决于预测变量的方差
  7. vim插件介绍(一)之Tabular
  8. 日用品 跟帖补充一个单词奖励1Euro,请勿恶意灌水,以保持帖子的连贯.
  9. Arduino驱动MAX30102心率血氧传感器模块
  10. Matlab 环境下用正弦波模拟方波和锯齿波
  11. 威纶通触摸屏做主站(客户端)与modsim虚拟从站通过MODBUS TCP/IP通讯测试
  12. exlsx表格教程_excel表格制作教程
  13. 出租房安装监控摄像头的必要性
  14. 基于RNN-LSTM模型的诗词生成/TensorFlow
  15. [SilkyBible] XviD系列-9
  16. 并发编程系列——3ThreadLocal核心原理分析
  17. Java Object 类方法解析
  18. Argox(立象)打印机
  19. php mysql ssl 连接_Mysql 中的SSL 连接
  20. 一文掌握Hibernate

热门文章

  1. HDU1166 敌兵布阵【树状数组】
  2. CCF NOI1002 三角形
  3. 字面量(literal)与 C 语言复合字面量(compound literals)
  4. python opencv3 —— 常用工具、辅助函数、绘图函数(图像添加文本、矩形等几何形状)
  5. 证明的思路 —— 数形结合
  6. 生活中的数学 —— 操场几何学
  7. 面向项目(十一)—— 库的使用
  8. spark1.0和2.0的区别_Spark2.1.0——Spark初体验
  9. java单例模式_Java 实现单例模式的 9 种方法
  10. 专科python应届生工资多少-应届毕业生自述面试15K月薪的Python后端开发经历,希望对你有用...