极简 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. MySQL引擎【转】

    数据库引擎介绍MySQL数据库引擎取决于MySQL在安装的时候是如何被编译的.要添加一个新的引擎,就必须重新编译MYSQL.在缺省情况下,MYSQL支持三个引擎:ISAM.MYISAM和HEAP.另外 ...

  2. npm修改默认配置

    1.nodejs在program files下面会造成一些项目中的问题,因此nodejs在program files下需要先卸载nodejs 2.安装nodejs到自定义的目录下面,例:D:\node ...

  3. 微信小程序自动化

    解析微信小程序 注意:若上面方法不行就使用下面的 小程序对应的chrome驱动版本包,2.4版本的

  4. Orleans 知多少 | Orleans 中文文档上线

    Orleans 简介 Orleans是一个跨平台框架,用于构建健壮,可扩展的分布式应用程序 Orleans建立在.NET开发人员生产力的基础上,并将其带入了分布式应用程序的世界,例如云服务. Orle ...

  5. 微信小程序如何快速开通流量主

    1.先开发小程序,小程序需要有亮点,毕竟新颖(这样别人才更好去点击查看) 2.条件是独立访客(UV)不低于1000,1000人说多不多,说少也不少,因为小程序是没有链接的,是不可以进行一个流量刷取的, ...

  6. 1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知

    乔丹是我听过的篮球之神,科比是我亲眼见过的篮球之神.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免 ...

  7. SDWebImage 清除磁盘缓存机制 iOS

    分析的版本 pod 'SDWebImage', '~> 5.0.6' SDWebImage默认清除磁盘缓存的时长是7天. /** * The maximum length of time to ...

  8. IOS 提审

    关于上架AppStore最后一步的“出口合规信息”.“内容版权”.“广告标识符”的选择 https://blog.csdn.net/ashimar_a/article/details/51745675

  9. 算法专题 | 10行代码实现的最短路算法——Bellman-ford与SPFA

    今天是算法数据结构专题的第33篇文章,我们一起来聊聊最短路问题. 最短路问题也属于图论算法之一,解决的是在一张有向图当中点与点之间的最短距离问题.最短路算法有很多,比较常用的有bellman-ford ...

  10. 9.深入k8s:调度器及其源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 源码版本是1.19 这次讲解的是k8s的调度器部分的代码,相对来说比较复杂,慢慢的梳理清 ...