极简 Node.js 入门 - 4.2 初识 stream

Node.js 诞生是为了解决 I/O 密集的 Web 性能问题,最常使用的两个模块就是文件系统和网络,而这两个模块都是 stream 的重度用户,streamNode.js入门到进阶的必经之路


Node.js 对 stream 是这样解释的

A stream is an abstract interface for working with streaming data in Node.js. The stream module provides an API for implementing the stream interface.


翻译过来流是 Node.js 中处理流式数据的抽象接口。 stream 模块提供了用于实现流接口的对象。基本就是用 stream 解释自己,第一次使用肯定不理解,但其实我们平时经常用到流

ls | grep *.js


命令的含义是 list 当前目录文件,把结果交给 grep 命令,按行筛选出拓展名是 js 的内容

使用 | 连接两条命令,把前一个命令的结果作为后一个命令的参数传入,这样数据像是水流在管道中传递,每个命令类似一个处理器,对数据做一些加工,因此 | 被称为“管道符”,这个处理过程就是流

从术语上讲流是对输入输出设备的抽象,是一组有序的、有起点和终点的字节数据传输手段


image.png


Node.js stream 类型

从程序角度而言流是有方向的数据,以程序为第一视角,按照流动方向可以分为三种流

设备流向程序:readable

程序流向设备:writable

双向流动:duplex、transform


NodeJS 关于流的操作被封装到了 Stream 模块,这个模块也被多个核心模块所引用。按照 Unix 的哲学:一切皆文件

普通文件(txt、jpg、mp4)

设备文件(stdin、stdout)

网络文件(http、net)


在 Node.js 中对文件的处理多数使用流来完成

小试牛刀

假设写程序某个功能需要读取某个配置文件 config.json,这时候简单分析一下

数据:config.json 的内容

方向:设备(物理磁盘文件) -> NodeJS 程序

应该使用 readable 流来做此事(后面章节会具体介绍 API)

const fs = require('fs'); const FILEPATH = '...'; const rs = fs.createReadStream(FILEPATH);


通过 fs 模块提供的 createReadStream() 方法创建了一个可读的流,这时候 _config.json_的内容从设备流向程序。示例并没有直接使用 Stream 模块,因为 fs 内部已经引用了 Stream 模块,并做了封装


读取到数据后可以对数据进行处理,比如需要写到某个路径 DEST ,这时候需要一个 writable 的流,让数据从程序流向设备

const ws = fs.createWriteStream(DEST);


两种流都有了,也就是两个数据加工器,那么如何通过类似 Unix 的管道符** | **来连接流呢?在 Node.js 中管道符号就是 pipe() 方法

const fs = require('fs'); const FILEPATH = 'DEST_PATH_IN_YOUR_DISK'; const rs = fs.createReadStream(FILEPATH); const ws = fs.createWriteStream(DEST); rs.pipe(ws);


这样利用流实现了简单的文件复制功能,关于 pipe() 方法的实现原理后面会提到,但有个值得注意地方:数据必须是从上游 pipe 到下游,也就是从一个 readable 流 pipe 到 writable 流

加工一下数据

上面提到了 readable 和 writable 的流称之为加工器,其实并不太恰当,因为过程中并没有加工什么,只是读取数据,然后存储数据

如果有个需求,把本地一个 package.json 文件中的所有字母都改为小写,并保存到同目录下的_ package-lower.json_ 文件下


这时候就需要用到双向的流了,假定有一个专门处理字符转小写的流 lowercase ,那么代码写出来大概是这样的

const fs = require('fs'); const rs = fs.createReadStream('./package.json'); const ws = fs.createWriteStream('./package-lower.json'); rs.pipe(lowercase).pipe(ws);


到这里就和清楚为什么称 pipe() 连接的流为加工器了,根据上面说的,必须从一个 readable 流 pipe 到 writable 流:

rs -> lowercase:lowercase 在下游,所以 lower 需要是个 writable 流

lowercase -> ws:相对而言,lowercase 又在上游,所以 lower 需要是个 readable 流


因此能够满足需求的 lowercase 必须是双向的流,具体使用 duplex 还是 transform 后面章节会提到。当然如果需求还有额外一些处理动作,比如字母还需要转成 ASCII 码,假定有一个双工流 ascii,那么代码简单改写

rs.pipe(lowercase).pipe(acsii).pipe(ws);


这样就完成了 package.json 文件字母转小写后处理成 acsii 码的需求

为什么应该使用 stream

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsswsg.html