假设我们现在要盖一座房子,我们买了一些砖块,厂家正在送货。现在我们有两个选择,一是等所有砖块都到了以后再开始动工;二是到一批砖块就开始动工,砖块到多少我们就用多少。

这两种方式哪种效率更高呢?显然是第二种。这就是流(stream)的理念。在计算机科学中,流是随时间可用的一系列数据元素。就像传送带运输物品一样,使用流可以实现一次处理一个数据元素。

在 NodeJS 中,stream 模块实现了流的功能。即使我们没有直接使用过这个模块,我们也间接使用过流,比如读写文件、网络等。

水流,信息流

信息就像水流一样,以比特流(strem of bits)的形式从一个地方流到另一个地方。比如读取文件,信息就从磁盘流向了应用程序。

但是,流的两端处理信息的速度是不同的,通常流的一端会比另一端要慢,因此就需要一个缓存来作为缓冲(buffer)。

如下图所示,上面的水龙头水流较大,下面的水龙头水流较小,因此需要一个容器来暂时存储下面的水龙头来不及处理的水。

NodeJS 中流的基本原理也是这样的,stream 模块实现了这些能力。

在 NodeJS 中有两种基本的流可以使用:

  • 可读流(Readable Streams)
  • 可写流(Writable Streams)

同时还有两种读写混合的流:

  • 双工流(Duplex Streams)-- 可读可写的流
  • 转换流(Transform Streams)-- 可以转换数据的双工流

可读流(Readable Stream)

可读流可以从一个地方读取数据,比如从文件中读取信息。读取的数据可以暂时存放在可读流中的缓存(Buffer)里,防止应用程序无法及时处理。

常见的可读流有 process.stdinfs.createReadStream 以及 HTTP 服务中的 IncomingMessage 对象。

可写流(Writable Stream)

可写流可以将数据写到一个地方,比如将数据写入文件中。为了防止因为写入目标处理速度太慢导致数据丢失,写入的数据可以暂存在可写流内部的缓存(Buffer)中。

常用的可写流有 process.stdoutprocess.stderrfs.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 的流的更多相关文章

  1. 一张图搞懂Spring bean的完整生命周期

    一张图搞懂Spring bean的生命周期,从Spring容器启动到容器销毁bean的全过程,包括下面一系列的流程,了解这些流程对我们想在其中任何一个环节怎么操作bean的生成及修饰是非常有帮助的. ...

  2. Nodejs学习笔记(三)——一张图看懂Nodejs建站

    前言:一条线,竖着放,如果做不到精进至深,那就旋转90°,至少也图个幅度宽广. 通俗解释上面的胡言乱语:还没学会爬,就学起走了?! 继上篇<Nodejs学习笔记(二)——Eclipse中运行调试 ...

  3. 一张图搞懂容器所有操作 - 每天5分钟玩转 Docker 容器技术(26)

    前面我们已经讨论了容器的各种操作,对容器的生命周期有了大致的理解,下面这张状态机很好地总结了容器各种状态之间是如何转换的. 如果掌握了前面的知识,要看懂这张图应该不难.不过有两点还是需要补充一下: 可 ...

  4. 一张图搞懂Ajax原理

    本文整理在,我的github上.欢迎Star. 原理 说起ajax,就不得不说他背后的核心对象XMLHttpRequest,而说到XMLHttpRequest我觉得,从它的readyState状态说起 ...

  5. 硬核!八张图搞懂 Flink 端到端精准一次处理语义 Exactly-once(深入原理,建议收藏)

    Flink 在 Flink 中需要端到端精准一次处理的位置有三个: Source 端:数据从上一阶段进入到 Flink 时,需要保证消息精准一次消费. Flink 内部端:这个我们已经了解,利用 Ch ...

  6. 三张图搞懂JavaScript的原型对象与原型链

    对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__proto__混淆,二来它们之间的各种指向实在有些复杂,其实市面上已经有非常多的文章在尝试说清楚,有一张 ...

  7. 三张图搞懂JavaScript的原型对象与原型链 / js继承,各种继承的优缺点(原型链继承,组合继承,寄生组合继承)

    摘自:https://www.cnblogs.com/shuiyi/p/5305435.html 对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__pro ...

  8. 一张图搞懂 Javascript 中的原型链、prototype、__proto__的关系 转载加自己的总结

    1. JavaScript内置对象 所谓的内置对象 指的是:JavaScript本身就自己有的对象 可以直接拿来就用.例如Array String 等等.JavaScript一共有12内置对象    ...

  9. 一张图搞懂javascript原型链

    js高级里面原型链对于新手来说并不友好,总的来说就是 任何函数都有自己的原型对象(prototype),任何实例对象都__proto__指向构造函数的原型 先来个最简单的原型三角关系 var fn = ...

随机推荐

  1. Golang写文件的坑

    Golang写文件一般使用os.OpenFile返回文件指针的Write方法或者WriteString或者WriteAt方法,但是在使用这三个方法时候经常会遇到写入的内容和实际内容有出入,因为这几个函 ...

  2. excel VBA一个fuction同时执行多个正则表达式,实现方法

    代码: Function zhengze3(ze1 As String, ze2 As String, Rng1 As Range, Rng2 As Range)    Set regx1 = Cre ...

  3. Ubuntu 之 Esc and CapsLK

    Ubuntu 更换 CapsLK 和 ESC 内容如下 // vim .xmodmap !! No Caps Lock clear lock !! Make Caps_lock an escape k ...

  4. Unity_DOTween

    DOTween官网 DG.Tweening.Ease枚举详解 2019.12.12补充: 问题:当前dotween动画没播放完,便再次播放有冲突的操作,如连续多次播放.正播.倒播,导致显示不正常或报错 ...

  5. kustomize简单使用

    1.背景 在Kubernetes v1.14版本的发布说明中,kustomize 成为了 kubectl 内置的子命令,并说明了 kustomize 使用 Kubernetes 原生概念帮助用户创作并 ...

  6. 40、如何获取yum安装时的rpm包

    1.先清除之前下载的数据包: [root@slave-db ~]#yum clean all 2.修改yum配置文件: [root@master-db ~]#vim /etc/yum.conf [ma ...

  7. HDU 4821 2013长春现场赛hash

    题意: 一个字符串S  问其中有几个子串能满足以下条件: 1.长度为M*L 2.可以被分成M个L长的小串  每个串都不一样 分析: hash方法,一个种子base,打表出nbase[i]表示base的 ...

  8. Retrofit使用Kotlin协程发送请求

    Retrofit2.6开始增加了对Kotlin协程的支持,可以通过suspend函数进行异步调用.本文简单介绍一下Retrofit中协程的使用 导入依赖 app的build文件中加入: impleme ...

  9. SpringMVC(7)格式化显示

    在SpringMVC(六)数据验证中我们介绍了如何验证提交的数据的正确性,当数据验证通过后就会被我们保存起来.保存的数据会用于以后的展示,这才是保存的价值.那么在展示的时候如何按照要求显示?(比如:小 ...

  10. 4shell中的特殊变量

    1.位置参数 2.其他特殊变量 2.1 用法举栗 2.2 $* 和 $@ 的区别 2.3 $?的用法 1.位置参数 运行 Shell 脚本文件时我们可以给它传递一些参数,这些参数在脚本文件内部可以使用 ...