几张图搞懂 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 = ...
随机推荐
- Unity3D学习笔记1——绘制一个三角形
目录 1. 绪论 2. 概述 3. 详论 3.1. 准备 3.2. 实现 3.3. 解析 3.3.1. 场景树对象 3.3.2. 绘制方法 4. 结果 1. 绪论 最近想学习一下Unity3d,无奈发 ...
- kube-controller-manager源码分析-PV controller分析
kubernetes ceph-csi分析目录导航 概述 kube-controller-manager组件中,有两个controller与存储相关,分别是PV controller与AD contr ...
- Linux云计算-01_介绍以及Linux操作系统安装
1 学习目的 兴趣爱好 技能提升 找到满意的工作 2 什么是云计算 云计算(cloud computing)是分布式计算的一种,指的是通过网络"云"将巨大的数据计算处理程序分解成无 ...
- POJ 1222 高斯消元更稳
大致题意: 有5*6个灯,每个灯只有亮和灭两种状态,分别用1和0表示.按下一盏灯的按钮,这盏灯包括它周围的四盏灯都会改变状态,0变成1,1变成0.现在给出5*6的矩阵代表当前状态,求一个能全部使灯灭的 ...
- SonarQube插件
关于插件我本身使用不多,如果看不惯英文界面,那么就先装个中文插件吧. 或者上微软的官方网站进行下载 将下载的插件上传到自己的sonarqube的服务的机器上,放置插件目录下,重启sonarqube即可 ...
- CentOS-Docker搭建Nacos-v1.3.2(单点)
说明:从v1.3.1版本开始支持自定义mysql-8.x 通用属性配置(v1.3.2) name description option MODE cluster模式/standalone模式 clus ...
- SpringBoot:WebSocket使用Service层的方法
方法一: 创建工具类 ApplicationContextRegister.java import org.springframework.beans.BeansException; import o ...
- 面试:Spring面试知识点总结
Spring知识点总结 1. 简介一下Spring框架. 答:Spring框架是一个开源的容器性质的轻量级框架.主要有三大特点:容器.IOC(控制反转).AOP(面向切面编程). 2. Spring框 ...
- 锁分析 Lock
锁分析 Lock NonReentrantLock ReadLock 共享锁 ReentrantLock 重入锁 排他锁 sync.lock 返回值为void,表示如无异常发生都认为锁获取成功 Fai ...
- Java实验项目四——多线程矩阵相乘算法的设计
Program:多线程矩阵相乘算法的设计 Description:利用多线程实现矩阵相乘,因为各个线程的运算互不影响, 所以不用使用锁,代码如下: thread.OperateMatrix类,实现矩阵 ...