Stream 的基础

零、参考资料

一、对概念的理解

"流"(stream)是一个很抽象的概念,《C程序设计语言》中这样定义:流与磁盘或其它外围设备关联的数据的源或目的地。 - 一样的费解。
从字面意义上,我们能找到的最贴合的是"水流",因此,通俗意义上就可以解释成像水流一样长长的一串的东西。
物质意义上的水流,是作为内容的水+作为外部的容器+时间共同形成,而在编程语言中的流,并不严格采用这三样基本元素。
编程语言中,流是对数据的一种描述,是一个视图,是一个对象,因此,很可能是先有一个视图(壳,空的容器),其次才与数据(水)关联,最后在时间的作用下数据逐步进入视图中,形成编程意义上的"流"
不过流不是容器,其内涵远不止容器,或者说,容器可以被视作一个流,以流的形式进行读写操作。

二、为什么会有流

流的概念挺抽象的,这个概念的产生自然有其应用场景。所以在 JavaScript 中,流是为何而产生?
其实很简单,数据从一个媒介进入到另外一个媒介是有成本的。我们都知道在计算机架构中,cpu 运算速度最快,但是不存储数据,硬盘存储数据,但是运算速度慢,所以这就有了时间差,即使在目前的架构中有内存、缓存等的设计,但是这个矛盾始终存在。而网络与计算机 cpu 的速度差就更明显了,总不能一直把 cpu 锁着,等网络数据全部加载完成再去进行运算。所以流就是在这样的需求下产生了。说白了,就是数据来一点,让 cpu 处理一点,数据没来,释放 cpu,让 cpu 去处理其他程序

三、js 中与流相关的术语和概念

(一)可读流

一个可读流是一个数据源,在 JavaScript 中用一个 ReadableStream 对象表示,数据从它的底层源(underlying source)流出——底层源表示一个你希望从中获取数据,且来自网络或其他域的某种资源(比如某些硬件的输出流)

  • chunk - 分块:FE 打包工具中也有这个概念,将各个组件分成独立单元,打包成独立文件,这样在运行时环境中可以按需加载。对于数据,也是类似的概念。数据(字节、类型化数组)被按序读入(分割成)很多小的片段,这些小的片段就是 chunk
  • enqueued - 入队:讲这些分块有序放入流中,这个操作就是入队 —— 这意味着它们已经在队列中排队等待被读取。流的一个内置队列跟踪了那些尚未读取的分块
  • reader - 读取流中分块的对象
  • controller - 与 reader 关联的控制对象,用来控制流(如:关闭流等操作)
  • consumer - 消费(程序):既然是可读的,那么就需要被读取,读取的过程即为 consumer,是 reader 和它一起运行的其他处理代码的综合过程
特点:
  • 同一时间,consumer 只能处理一个分块,但是能对这个分块进行任何类型的操作
  • 一个(可读)流只能被一个 reader 读取,如果想被其他 reader 读取,必须想取消掉前一个 reader
根据这些特点,拷贝(teeing)就应运而生:将一个流分割成两个相同的副本,这两个副本从内容上与分割器前的流是完全相同的,因此就能被两个独立的 reader 读取,这个分割过程就是拷贝

(二)可写流

与可读流相对,其概念上是与可读流相反,是对外输出的

(三)其他

  • 链式管道(pipe chain)传输:这个概念呢着重在转换上,说白了就是将一个流转成另外一个流,当然在过程中开发者可以完成数据的操作。因此在这个过程中流至少是成对出现的。链式管道传输的起点称为原始源(original source),终点称为最终接收器(ultimate sink)
  • 背压(backpressure):单个流或一个链式管道调节读/写速度的过程。当链中后面的一个流仍然忙碌,尚未准备好接受更多的分块时,它会通过链向上游的流发送一个信号,告诉上游的转换流(或原始源)适当地减慢传输速度,这样你就不会在任何地方遇到瓶颈 - 这个似乎与硬件相关,实际编程中用处似乎不大

四、流和缓冲区

这两个从功能上没什么太大的差别,都是为了解决不同运算速度的设备之间的数据传输问题,但是流更接近抽象的层面,属于程序/语言方面。而缓冲区则更偏向硬件的层面,在 MDN 中("缓冲区是物理内存中的一个存储区域,当数据进行转移时用来临时存放数据")中直接就将其简化成物理内存的一块区域

因此,如果这样来看的话,在程序软件到硬件的整条链路中,流的处理速度会比缓冲区的更快一点,即:程序 - 流 - 缓冲区 - 硬件
posted @ 2024-03-05 14:49  shiweiqianju  阅读(1)  评论(0编辑  收藏  举报