极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node

本文更佳阅读体验:https://www.yuque.com/sunluyong/node/stream

stream 概念

Node.js 诞生是为了解决 I/O 密集的 Web 性能问题,最常使用的两个模块就是文件系统和网络,而这两个模块都是 stream 的重度用户,stream 是 Node.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 的内容

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

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


Node.js stream 类型

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

  1. 设备流向程序:readable
  2. 程序流向设备:writable
  3. 双向流动:duplex、transform

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

  1. 普通文件(txt、jpg、mp4)
  2. 设备文件(stdin、stdout)
  3. 网络文件(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

有个用户 Web 在线看视频的场景,假定我们通过 HTTP 请求返回给用户电影内容,那么代码可能写成这样

const http = require('http');
const fs = require('fs'); http.createServer((req, res) => {
fs.readFile(moviePath, (err, data) => {
res.end(data);
});
}).listen(8080);

这样的代码又两个明显的问题

  1. 电影文件需要读完之后才能返回给客户,等待时间超长
  2. 电影文件需要一次放入内存中,内存吃不消

使用 stream 可以把电影文件一点点的放入内存中,然后一点点的返回给客户(利用了 HTTP 协议的 Transfer-Encoding: chunked 分段传输特性),用户体验得到优化,同时对内存的开销明显下降

const http = require('http');
const fs = require('fs'); http.createServer((req, res) => {
fs.createReadStream(moviePath).pipe(res);
}).listen(8080);

除了上述好处,stream 还让代码优雅了很多,功能逻辑独立,拓展也比较简单。比如需要对视频内容压缩,我们可以引入一个专门做此事的流,这个流不用关心其它部分做了什么,只要是接入管道中就可以了

const http = require('http');
const fs = require('fs');
const oppressor = require(oppressor); http.createServer((req, res) => {
fs.createReadStream(moviePath)
.pipe(oppressor)
.pipe(res);
}).listen(8080);

可以看出来使用流后代码逻辑变得相对独立,可维护性也会有一定的改善

极简 Node.js 入门 - 4.2 初识 stream的更多相关文章

  1. 极简 Node.js 入门 - Node.js 是什么、性能有优势?

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  2. 极简 Node.js 入门 - 1.2 模块系统

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  3. 极简 Node.js 入门 - 1.3 调试

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  4. 极简 Node.js 入门 - 1.4 NPM & package.json

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  5. 极简 Node.js 入门 - 2.1 Path

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  6. 极简 Node.js 入门 - 2.2 事件

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  7. 极简 Node.js 入门 - 2.3 process

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  8. 极简 Node.js 入门 - 2.4 定时器

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  9. 极简 Node.js 入门 - 3.1 File System API 风格

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

随机推荐

  1. ThinkCMF&Thinkphp 首页静态化处理

    基于TP5的页面静态化教程 1.首页在控制器添加以下代码生成静态页面文件 $info = $this->buildHtml("/staticIndex", 'static', ...

  2. SpringBoot整合Redis并完成工具类

    SpringBoot整合Redis的资料很多,但是我只需要整合完成后,可以操作Redis就可以了,所以不需要配合缓存相关的注解使用(如@Cacheable),而且我的系统框架用的日志是log4j,不是 ...

  3. Hop: Heterogeneity-aware Decentralized Training

    郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! 以下是对本文关键部分的摘抄翻译,详情请参见原文. ASPLOS 2019 Abstract 最近的研究表明,在机器学习的背景下,去中心化算 ...

  4. 设计模式 | Spring中用到的设计模式,你知道几个?

    设计模式无处不在,因为它就来自于我们的日常生活,提炼于生活经验. 正握在你手中的手机,不能用220V的电压直接充电,需要一个专门的电源适配器(充电器)才行.摆在你桌上的电脑也是一样的,都需要" ...

  5. java面试题0001-区分类中重载方法的依据是什么?

    到底是根据什么来区分类中重载的方法? A:不同的返回值类型. B:不同的参数类型. C:不同的访问权限. D:不同的参数名称. 解析: Step1.我们先在DifferentiateReload类中创 ...

  6. Ant Design Vue使用支持v-model效验的FormModel表单遇到的一个坑

    按照官网上用法写好表单后,在a-select上绑定了change事件 <a-form-model-item label="类型" prop="config.type ...

  7. CSS布局中浮动问题的四种解决方案

    一.起因: 子盒子设置浮动之后效果: 由此可见,蓝色的盒子设置浮动之后,因为脱离了标准文档流,它撑不起父盒子的高度,导致父盒子高度塌陷.如果网页中出现了这种问题,会导致我们整个网页的布局紊乱 二.解决 ...

  8. e3mall商城总结13之订单确认(有BUG)

    说在前面的话 上一节说了购物车的生成,本节主要说了在购物车的列表上去结算,从而生成一个未支付的订单,生成的订单默认状态为1, 题目说的BUG是因为所有数据都是通过前端向后端生成的,包括订单的金额.因此 ...

  9. java 注解开发

    目录 注解 JDK自带的注解三个 注解分类 按照运行机制 按照来源分类 自定义注解的语法要求 元注解 解析注解 获取注解的注解 Spring中的注解 组合注解 注解 JDK自带的注解三个 @Overr ...

  10. qdu-凑数题(01背包)

    Description 小Q手里有n(n<=1000) 个硬币,每枚硬币有一定的金额(200=>x>=1)他想知道,用这些硬币(每枚硬币只能用一次,但可能会有等面值的用两次) 能组成 ...