几张图搞懂 NodeJS 的流
假设我们现在要盖一座房子,我们买了一些砖块,厂家正在送货。现在我们有两个选择,一是等所有砖块都到了以后再开始动工;二是到一批砖块就开始动工,砖块到多少我们就用多少。
这两种方式哪种效率更高呢?显然是第二种。这就是流(stream)的理念。在计算机科学中,流是随时间可用的一系列数据元素。就像传送带运输物品一样,使用流可以实现一次处理一个数据元素。
在 NodeJS 中,stream 模块实现了流的功能。即使我们没有直接使用过这个模块,我们也间接使用过流,比如读写文件、网络等。
水流,信息流
信息就像水流一样,以比特流(strem of bits)的形式从一个地方流到另一个地方。比如读取文件,信息就从磁盘流向了应用程序。
但是,流的两端处理信息的速度是不同的,通常流的一端会比另一端要慢,因此就需要一个缓存来作为缓冲(buffer)。
如下图所示,上面的水龙头水流较大,下面的水龙头水流较小,因此需要一个容器来暂时存储下面的水龙头来不及处理的水。

NodeJS 中流的基本原理也是这样的,stream 模块实现了这些能力。
在 NodeJS 中有两种基本的流可以使用:
- 可读流(Readable Streams)
- 可写流(Writable Streams)
同时还有两种读写混合的流:
- 双工流(Duplex Streams)-- 可读可写的流
- 转换流(Transform Streams)-- 可以转换数据的双工流
可读流(Readable Stream)
可读流可以从一个地方读取数据,比如从文件中读取信息。读取的数据可以暂时存放在可读流中的缓存(Buffer)里,防止应用程序无法及时处理。

常见的可读流有 process.stdin、fs.createReadStream 以及 HTTP 服务中的 IncomingMessage 对象。
可写流(Writable Stream)
可写流可以将数据写到一个地方,比如将数据写入文件中。为了防止因为写入目标处理速度太慢导致数据丢失,写入的数据可以暂存在可写流内部的缓存(Buffer)中。

常用的可写流有 process.stdout、process.stderr 和 fs.createWriteStream.
双工流(Duplex Streams)
双工流是可读流和可写流的混合体。连接到双工流之后,应用程序既可以从流中读取数据,也可以向流中写入数据。在双工流中,可读流和可写流有各自独立的缓存(Buffer)。

最常用的双工流就是 net.Socket。
转换流(Transform Stream)
转换流是更加特殊的混合流,在转换流中,可读流是通过某种方式连接到可写流上的。

最常见的转换流是有 Cipher 创建的流。在这个流中,应用程序写入数据,然后再从流中读取加密后的数据。
管道(Pipe)
通常流在连接到一起之后才能发挥更大的作用。我们通过管道来连接流。
比如我们可以将一个可读流连接到一个可写流或者双工流上,仅仅使用可读流的 pipe() 方法即可。
常见的管道场景就是复制文件。将 fs.createReadStream() 创建的流通过 pipe() 方法连接到 fs.createWriteStream() 创建的流上去。
使用流复制数据
我们可以将流连接到多个其他流上。这在一些需要重复读取原始数据的场景中非常有用。因为可读流只能读取一次数据,因此我们可以通过 pipe() 方法将可读流连接到多个流上,这样这些被连接的流就可以直接消费数据,不需要创建多个可读流。
const fs = require('fs')
const original = fs.createReadStream('./original.txt')
const copy1 = fs.createWriteStream('./copy1.txt')
const copy2 = fs.createWriteStream('./copy2.txt')
original.pipe(copy1)
original.pipe(copy2)

高水位线控制(highWaterMark)
在最开始的例子中,我们通过水箱蓄水的例子描述了流的缓存特性。因为上方的水流始终比下方的水流快,水箱中的水越来越多,终究会超过水箱的容积而溢出。
因此我们需要一个高水位警戒线,当水箱中的水位高于这个警戒线的时候,就需要通知上方的水龙头暂时停止放水了。

在流中也是同样的原理,可读流和可写流内部都有缓存,这些缓存的最大可存储量是系统的可用内存量。NodeJS 流通过 highWaterMark 这个配置项来控制缓存中的水位线。
举个例子,如下图,可读流连接到可写流之后,可写流通过 highWaterMark 来检测缓存中的水位是否过高,高于这条线之后,可写流会通知可读流暂停写入数据。

需要注意的是,highWaterMark 只是一个警示线,并不是一个硬性约束条件。也就是说,如果自定义的流没有正确处理这个警示线的话,可能会导致数据丢失。
流的应用
我们来举个例子综合说明如何使用流。
假设我们有一个裁减图片的应用程序。用户将图片的地址告诉应用程序,应用程序从网络上读取原始图片,裁减之后再返回给用户。那么我们可以借助于流来实现这个应用程序的功能,如下图。

常见面试知识点、技术解决方案、教程,都可以扫码关注公众号“众里千寻”获取,或者来这里 https://everfind.github.io 。

几张图搞懂 NodeJS 的流的更多相关文章
- 一张图搞懂Spring bean的完整生命周期
一张图搞懂Spring bean的生命周期,从Spring容器启动到容器销毁bean的全过程,包括下面一系列的流程,了解这些流程对我们想在其中任何一个环节怎么操作bean的生成及修饰是非常有帮助的. ...
- Nodejs学习笔记(三)——一张图看懂Nodejs建站
前言:一条线,竖着放,如果做不到精进至深,那就旋转90°,至少也图个幅度宽广. 通俗解释上面的胡言乱语:还没学会爬,就学起走了?! 继上篇<Nodejs学习笔记(二)——Eclipse中运行调试 ...
- 一张图搞懂容器所有操作 - 每天5分钟玩转 Docker 容器技术(26)
前面我们已经讨论了容器的各种操作,对容器的生命周期有了大致的理解,下面这张状态机很好地总结了容器各种状态之间是如何转换的. 如果掌握了前面的知识,要看懂这张图应该不难.不过有两点还是需要补充一下: 可 ...
- 一张图搞懂Ajax原理
本文整理在,我的github上.欢迎Star. 原理 说起ajax,就不得不说他背后的核心对象XMLHttpRequest,而说到XMLHttpRequest我觉得,从它的readyState状态说起 ...
- 硬核!八张图搞懂 Flink 端到端精准一次处理语义 Exactly-once(深入原理,建议收藏)
Flink 在 Flink 中需要端到端精准一次处理的位置有三个: Source 端:数据从上一阶段进入到 Flink 时,需要保证消息精准一次消费. Flink 内部端:这个我们已经了解,利用 Ch ...
- 三张图搞懂JavaScript的原型对象与原型链
对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__proto__混淆,二来它们之间的各种指向实在有些复杂,其实市面上已经有非常多的文章在尝试说清楚,有一张 ...
- 三张图搞懂JavaScript的原型对象与原型链 / js继承,各种继承的优缺点(原型链继承,组合继承,寄生组合继承)
摘自:https://www.cnblogs.com/shuiyi/p/5305435.html 对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__pro ...
- 一张图搞懂 Javascript 中的原型链、prototype、__proto__的关系 转载加自己的总结
1. JavaScript内置对象 所谓的内置对象 指的是:JavaScript本身就自己有的对象 可以直接拿来就用.例如Array String 等等.JavaScript一共有12内置对象 ...
- 一张图搞懂javascript原型链
js高级里面原型链对于新手来说并不友好,总的来说就是 任何函数都有自己的原型对象(prototype),任何实例对象都__proto__指向构造函数的原型 先来个最简单的原型三角关系 var fn = ...
随机推荐
- 旋转的球(animation与 transform)
<html> <head> <meta http-equiv="Content-Type" content="text/html; char ...
- Arduino库和STM32的寄存器、标准库、HAL库、LL库开发比较之GPIO
标题: Arduino库和STM32的寄存器.标准库.HAL库.LL库开发比较之GPIO 作者: 梦幻之心星 sky-seeker@qq.com 标签: [#Arduino,#STM32,#库,#开发 ...
- kustomize简单使用
1.背景 在Kubernetes v1.14版本的发布说明中,kustomize 成为了 kubectl 内置的子命令,并说明了 kustomize 使用 Kubernetes 原生概念帮助用户创作并 ...
- 『无为则无心』Python序列 — 18、Python列表概念及常用操作API
目录 1.列表的概念 (1)列表的定义 (2)列表的应用场景 (3)列表的定义格式 2.列表的常用操作 (1)列表的查找 1)通过下标查找 2)通过方法查找 3)判断是否存在 (2)列表的增加 @1. ...
- MiniSMB 专业网络性能测试仪表 英特尔82576 4*1GE 网卡性能测试报告
MiniSMB 专业网络性能测试仪表英特尔82576 4*1GE网卡性能测试报告 一.测试环境 测试配置 ①工控机配置: CPU:Intel(R) Core(TM) i7-6800K CPU @ 3. ...
- 11、gitlab和Jenkins整合(2)
5.补充: (1)构建说明: 1)Jenkins会基于一些处理器任务后,构建发布一个稳健指数 (从0-100 ),这些任务一般以插件的方式实现. 2)它们可能包括单元测试(JUnit).覆盖率(Cob ...
- Gym 101147G 第二类斯特林数
大致题意: n个孩子,k场比赛,每个孩子至少参加一场比赛,且每场比赛只能由一个孩子参加.问有多少种分配方式. 分析: k>n,就无法分配了. k<=n.把n分成k堆的方案数乘以n的阶乘.N ...
- Springboot:Springboot+mysql5.7搭建服务,超过8小时连接mysql失败
报错信息 2017-03-12 03:00:02.539 ERROR 9311 --- [nio-9000-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] ...
- springCloud学习05之api网关服务zuul过滤器filter
前面学习了zuul的反向代理.负载均衡.fallback回退.这张学习写过滤器filter,做java web开发的对filter都不陌生,那就是客户端(如浏览器)发起请求的时候,都先经过过滤器fil ...
- ESP32音频输入-MAX4466,MAX9814,SPH0645LM4H,INMP441(翻译)
有几种方法可以将模拟音频数据输入到ESP32中. 直接从内置的模数转换器(ADC)读取 这对于一次性读取很有用,但不适用于高采样率. 使用I2S通过DMA读取内置ADC 适用于模拟麦克风,例如MAX4 ...