前言
在做 SSR Stream Render 的时候遇到了 Node.js 的 Stream,但是对其总是一知半解。正好最近 ChatGPT 很火,找他学一学吧,没想到真的把我教会了。PS:文末有跟 ChatGPT 的精彩对话(请忽略我稀烂的英语)。
为什么需要 Stream
首先我们通过一个简单的例子来说明一下,使用流的好处。如下所示,我们将一个大文件读取到另一个文件中:
const fs = require('fs')
fs.readFile('./big.file', (err, data) => {if (err) throw errfs.writeFile('./out', data, () => {})
})
通过活动监视器,我们发现该进程内存占用为 300 MB 左右:
如果,我们换成流,情况就不一样了:
const fs = require('fs')
const readStream = fs.createReadStream('./big.file')
const writeStream = fs.createWriteStream('./out')
readStream.pipe(writeStream)
看来 Stream 在处理大数据的时候是非常好的工具,接下来就让我们通过打比方的方式来进行理解吧。
通过比喻来理解 Stream
Readable Stream
首先,对于 Readable Stream
,我们可以把他比喻成一个水龙头:
水龙头的水来自于哪,需要具体的 Readable Stream
来实现。比如 fs.createReadStream
创建的 Readable Stream
其水源自于文件,process.stdin
水源自于标准输入。
两个状态 flowing 和 paused
水龙头有两个状态 flowing
和 paused
,即龙头打开或关闭。初始化一个 Readable Stream
时,默认是关闭的:
const readStream = fs.createReadStream('./file')
console.log(readStream._readableState.flowing, readStream._readableState.paused) // false true
当我们监听 data
事件时,会自动打开开关:
const readStream = fs.createReadStream('./file')
readStream.on('data', (chunk) => {console.log(chunk)
})
console.log(readStream._readableState.flowing, readStream._readableState.paused) // true false
且会通知水源往龙头中灌水,这样,水就流到了 data
事件的回调函数中:
我们也可以通过 resume
方法来手动开启水龙头,不过要小心,有可能导致水丢失:
const readStream = fs.createReadStream('./file')
readStream.resume()
setTimeout(() => {readStream.on('data', console.log) // 打印为空
}, 1000)
这就好比先把水龙头打开了,然后再放桶子,肯定会漏掉一些水。
当然,我们也可以调用 pause
关闭水龙头,比如下面这个例子在接收到第一批水后就关闭了水龙头:
const readStream = fs.createReadStream('./big.file')
readStream.once('data', (chunk) => {readStream.pause()
})
buffer
上面代码调用 pause
后水源的水不会停止,会流到水龙头的一个 buffer
中,直到达到 highWaterMark
(最高水位线)则停止:
我们可以通过代码验证一下:
const readStream = fs.createReadStream('./big.file')
readStream.once('data', (chunk) => {readStream.pause()setTimeout(() => {console.log(readStream._readableState.length, // 水龙头 buffer 的大小readStream._readableState.highWaterMark // 最高水位线) // 65536 65536}, 1000)
})
而且,我们可以重新再次打开水龙头,此时会先消耗掉 buffer
中的水,然后再从源头读取,比如下面这个例子(文末 ChatGPT 给的例子也可以):