前言
对于前端同学来讲流的概念还是不太好理解的。然而stream模块是node中一个非常重要的模块。
- node官方文档上是这么描述的:流(stream)是一种在 Node.js 中处理流式数据的抽象接口 那我们姑且这么理解吧:流就是为我们提供了一套处理数据的工具。
为什么要使用流
既然流是一种处理数据的工具,那肯定还有其他的工具可以供我们使用啊,那为什么我们非要用流来处理呢?或者说使用流能有啥好处?话不多说,上代码:
const path = require('path')const http = require('http')const fs = require('fs')http.createServer((req, res) => { fs.readFile(path.resolve(__dirname, 'src/data.txt'), (err, data) => { if (!err) { res.setHeader('Content-Type', 'text/plain;charset=utf-8') // 设置编码 res.end(data) } })}).listen(8888)复制代码
上面的代码看着貌似没有问题。假设我们读取的不是一个.txt的文件,而是一个.mp4格式的视频呢?实际上我们读取的数据会存放在内存中,那上面的代码就会大量消耗内存。那有没有什么办法解决呢?这个时候,流这个工具就派上用场啦,他可以实现把资源一块一块的往内存中运输,然后在一块一块的从内存中取出来。这样就不会出现一次性把文件都放入内存中啦。
node中四种基本流类型
node中提供了四种基本的流类型分别是:
- Readable - 可读的流 (例如 fs.createReadStream()).
- Writable - 可写的流 (例如 fs.createWriteStream()).
- Duplex - 可读写的流 (例如 net.Socket).
- Transform - 在读写过程中可以修改和变换数据的 Duplex 流(例如zlib.createDeflate())
我们这里只讨论可读流和可写流。
可读流
可读流有两种模式:
流动模式
从系统底层读取数据并push()到缓存池,达到highWaterMark后 push() 返回 false,资源停止流向缓存池,并触发data事件消费数据。切换到Flowing模式的方法:
- 监听data事件
- 调用stream.resume()方法
- 调用stream.pipe()方法将数据发送到 Writable
暂停模式
Stream 默认是Paused模式,必须显式调用stream.read()方法来从流中读取数据。每一次数据达到缓存池都会触发一次 readable 事件,也就是每一次 push() 都会触发 readable。切换到Paused模式的方法:
- 监听readable事件
- 如果不存在管道目标(pipe destination),调用 stream.pause()
- 如果存在管道目标,取消 data 事件监听,并调用 stream.unpipe() 方法移除所有管道
我们前言部分的代码就可以稍稍改造一下啦!
const path = require('path')const fs = require('fs')const http = require('http')http.createServer((req, res) => { fs.createReadStream(path.resolve(__dirname, 'src/data.txt'))})复制代码
可写流
原理与 Readable Stream 是比较相似的,数据流过来的时候,会直接写入到资源池,当写入速度比较缓慢或者写入暂停时,数据流会进入队列池缓存起来
当生产者写入速度过快,把队列池装满了之后,就会出现「背压」,这个时候是需要告诉生产者暂停生产的,当队列释放之后,Writable Stream 会给生产者发送一个 drain 消息,让它恢复生产。