流(stream)是 Node.js 中处理流式数据的抽象接口。流也是一种数据传输手段,是有顺序的,有起点和终点的,比如你要把数据从一个地方传到另一个地方。
为什么流那么好用还那么重要呢?
stream
模块是 Node.js 中用于构建实现了流接口的对象。
🌰 示例:访问 stream
模块
const stream = require('stream');
尽管理解流的工作方式很重要,但是 stream
模块主要用于开发者创建新类型的流实例。 对于以消费流对象为主的开发者,极少需要直接使用 stream
模块。
在 Node.js 中对文件的处理多数使用流来完成:
在 Node.js 中有四种基本的流类型:
Writable
(可写流):可写流是数据可以被写入目标的抽象(例如 fs.createWriteStream()
)Readable
(可读流):可读流是数据可以被消费的抽象(例如 fs.createReadStream()
)Duplex
(双向流):双向流既是可写的也是可读的(例如 net.Socket
)Transform
(转换流):转向流基于双向流,可以在读或写的时候被用来更改或者转换数据(例如 zlib.createDeflate()
使用 gzip 算法压缩数据)。可以将转换流想象成一个函数,它的输入是可写流,输出是可读流。某些地方也将转换流称为通过流(through streams)。const Stream = require('stream');const Readable = Stream.Readable;const Writable = Stream.Writable;const Duplex = Stream.Duplex;const Transform = Stream.Transform;
所有流都是 EventEmitter 的实例。触发它们的事件可以读或者写入数据,然而,我们可以使用 pipe
方法消费流的数据。
此外,该模块还包括实用函数 stream.pipeline()
、stream.finished()
和 stream.Readable.from()
方法。
Node.js 创建的流都是运作在字符串和 Buffer(或 Uint8Array)上。 当然,流的实现也可以使用其它类型的 JavaScript 值(除了 null)。 这些流会以 对象模式
进行操作。
当创建流时,可以使用 objectMode
选项把流实例切换到对象模式。 将已存在的流切换到对象模式是不安全的。
可写流
和 可读流
都会在内部的缓冲器中存储数据,可以分别使用的 writable.writableBuffer
或 readable.readableBuffer
来获取。
可缓冲的数据大小取决于传入流构造函数的 highWaterMark
选项。
highWaterMark
指定了字节的总数highWaterMark
指定了对象的总数当调用 stream.push(chunk)
时,数据会被缓冲在可读流中。 如果流的消费者没有调用 stream.read()
,则数据会保留在内部队列中直到被消费。
一旦内部的可读缓冲的总大小达到 highWaterMark
指定的阈值时,流会暂时停止从底层资源读取数据,直到当前缓冲的数据被消费 (也就是说,流会停止调用内部的用于填充可读缓冲的 readable._read()
)。
当调用 writable.write(chunk)
时,数据会被缓冲在可写流中。 当内部的可写缓冲的总大小小于 highWaterMark
设置的阈值时,调用 writable.write()
会返回 true
。 一旦内部缓冲的大小达到或超过 highWaterMark
时,则会返回 false
。
stream API 的主要目标,特别是 stream.pipe()
,是为了限制数据的缓冲到可接受的程度,也就是读写速度不一致的源头与目的地不会压垮内存。
因为 Duplex 和 Transform 都是可读又可写的,所以它们各自维护着两个相互独立的内部缓冲器用于读取和写入, 这使得它们在维护数据流时,读取和写入两边可以各自独立地运作。 例如,net.Socket 实例是 Duplex 流,它的可读端可以消费从 socket 接收的数据,而可写端则可以将数据写入到 socket。 因为数据写入到 socket 的速度可能比接收数据的速度快或者慢,所以读写两端应该独立地进行操作(或缓冲)。
参考资料: