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

这两种方式哪种效率更高呢?显然是第二种。这就是流(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. 32.qt quick-PathView实现好看的home界面

    pathView的使用类似与ListView,都需要模型(model)和代理(delegate),只不过pathView多了一个路径(path)属性,顾名思义路径就是item滑动的路径. 一个Path ...

  2. 深入学习Netty(1)——传统BIO编程

    前言 之前看过Dubbo源码,Nacos等源码都涉及到了Netty,虽然遇到的时候查查资料,后面自己也有私下学习Netty并实践,但始终没有形成良好的知识体系,Netty对想要在Java开发上不断深入 ...

  3. Unity3D学习笔记2——绘制一个带纹理的面

    目录 1. 概述 2. 详论 2.1. 网格(Mesh) 2.1.1. 顶点 2.1.2. 顶点索引 2.2. 材质(Material) 2.2.1. 创建材质 2.2.2. 使用材质 2.3. 光照 ...

  4. ES6深度解析3:Generators

    介绍ES6 Generators 什么是Generators(生成器函数)?让我们先来看看一个例子. function* quips(name) { yield "hello " ...

  5. 组建Redis集群遇到`GLIBC_2.14' not found和ps -ef 不显示用户名

    RHEL6.9组建Redis sentinel集群遇到两个问题 今天在组件Redis sentinel 集群时,遇到两个问题,之前已经组建多次,都没碰到类似问题,在解决这两个问题时,耗费些时间. 问题 ...

  6. 精尽Spring Boot源码分析 - @ConfigurationProperties 注解的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  7. DRF之过滤排序分页异常处理

    一.过滤 对于列表数据要通过字段来进行过滤,就需要添加 django-filter 模块 使用方法: # 1.注册,在app中注册 settings.py INSTALLED_APPS = [ 'dj ...

  8. CentOS-关闭防火墙和禁用安全策略

    关闭防火墙 默认使用的是firewall作为防火墙 查看防火墙状态 $ firewall-cmd --state 停止firewall $ systemctl stop firewalld.servi ...

  9. phpRedisAdmin安装

    1.安装gitsudo yum install yum 2.安装依赖包:yum install -y php php-devel php-fpm php-mysql php-gd libjpeg* p ...

  10. 使用Less/Sass生成Bootstrap格栅样式系统

    熟悉Bootstrap的同学应该了解其中的格栅系统,用来排版非常方便.他将页面分为12等分,并且适用不同的尺寸屏幕.超小xs(小于768px),小屏sm(大于等于768px),中屏md(大于等于992 ...