详解Node中Stream模块之可读流Readable(一)

前言

从这一篇文章起我们开始聊一聊Stream。

为什么要好好学习一下这个模块呢?

因为它是Node模块基础中的基础,许多其他的模块都继承了它,比如fs、process、http等,另外目前很多的构建工具比如gulp等它们的插件都是基于Stream开发的,如果你不理解Stream,那只能照葫芦画瓢!

它有什么作用呢?

在传输数据时,当遇到数据很大的时候很难一次性传递完成,这个时候可以利用流将数据分成一块一块的,分片段传输,比如你用Node开发一个向服务器同步本地文件这样一个工具!

Stream分类

Stream模块主要提供了五种类型的流:

图1

这篇文章主要分析一下Readable。

基类Stream

Node模块有一个Event基础模块,所有继承它的模块都具有事件能力,这里Stream也继承了这个模块,并且实现了一个方法pipe。

图2

上面几种流继承了这个基类并且重写了pipe方法!

Stream的驱动流程

图3

Stream对数据的处理流程就像水泵抽水一样

  • Read方法 <-> 水泵
  • 数据 <-> 水源
  • 缓冲区 <-> 抗旱沟
  • 事件 <-> 稻田闸门
  • 消费区 <-> 稻田
  • 想想这样的生活场景:南方稻田在插秧的季节需要很多的水,一般生产队都会在大湖岸边架设大型水泵,水泵抽上来的水多数情况下不会直接放进稻田,水泵口大多都会对准抗旱沟,抗旱沟连接各个稻田,当稻田前的抗旱沟已经蓄有一定量的水时就可以打开闸门把水放入稻田。

    Stream的逻辑和这个场景很像!

    Readable写法

    可读流主要负责把数据泵进缓存区,再把缓存区的数据传出去,但是不负责消费,调用时的写法有两种:

    图4

    注意:图4中,你会发现传参形式的写法方法名字前没有下划线,继承方式有下划线,原因如下:

    图5

    原因一目了然,内部用的始终都是有下划线的方法。

    让Readable动起来

    图6

  • 读数据的逻辑都在read方法里面开发,当可读流动起来时会自动运行这个方法;
  • 从图5中可以看出这个方法的this指向可读流对象;
  • 想把数据传递到可读流内部,还需要调用push方法,其实还有一个方法unshift,和数组的同名方法类似,push是把数据放在缓存区的结尾,unshift是把数据放在缓存区的开头;
  • 如果想要告诉可读流对象数据已经读完,必须push(null),此操作会将内部状态ended置为true,此后再push数据就会无效
  • 如果只是这样,可读流是动不起来的,因为还必须让读进去的数据有消费的场所。

    有没有遇到这样的生活场景:你装一桶水,里面插一个软管,水是不会自动流出来的,必须用嘴巴吸一口,水才会自己流动。

    这个吸一口的动作在这里就是绑定一个data事件

    图7

    这个绑定事件不仅仅只有一个绑定动作,还有一个开关的逻辑,这个开关逻辑就是resume,resume方法会触发一个循环读取逻辑。

    图8 逻辑简写便于说明

    图9 逻辑简写便于说明

    可读流对象原型上的read方法被循环调用,在这个过程中它会调用初始化时我们传入的_read方法,这个方法的内容是我们自己定义的。如上所述,只要push(null),state.ended就会被置为true,那么就不会再从外面读取数据。

    另外push方法会把读取的数据存入缓存区,fromList方法会把数据从缓存区取出来,

    图10 逻辑简写

    取出的数据只要不为空就会触发data事件,将数据传给消费区(消费区如图7)。

    总结

    这篇文章主要简单的介绍了Stream模块和可读流Readable,里面很多的细节都没有提到,后面会一点点展开,尽量让每个人都能看明白!

    喜欢我的文章就关注我吧,有问题可以发表评论,我们一起学习,共同成长!