概要
Nodejs 可读流是 Stream 接口下的一个重要分支,可读流承载着数据源头的作用,属于流生产消费的上游
可读流对象内部维护着一个 Buffer 空间,该空间负责缓存可读流使用 _read 方法读取的数据
起初,我认为该缓冲区的大小最大也就是 highWaterMark 参数所指定的大小,但实际实验后发现,该缓冲区大小范围实际上在(0 <= x < 2 * highWaterMark)
原理分析
highWaterMark 参数可以指定可读流使用 _read 方法读取数据时一次读取的数据大小,为了更加明显的看到缓冲区的大小变化,我这里将默认 fs 可读流的 64kb 设为了 1M
const fs = require('fs')
// test1.txt 大小在 9700 kb左右
const rd = fs.createReadStream('test1.txt', {
highWaterMark: 1000 * 1024
})
rd.on('readable', () => {
let data
console.log(rd.read(1).toString())
console.log('-------', rd._readableState.length)
})
// 0
// ------- 1023999
// 1
// ------- 2047998
如上,我们指定了 highWaterMark 大小为 1M,因为 test1.txt 文件体积在 10M 左右,完全足够 rd 读取,因此当 rd 触发第一次的 readable 事件时,可读流内部所维护的缓冲区大小为 1000 * 1024 kb
此时,触发了指定的回调函数,其内部可以看到使用 read 方法读取了一个字节的数据,并且之后便不再读取了
但是从打印结果可以看到,明显回调函数触发了两次,之所以会触发两次,便是因为第一次的读取一个字节的操作,虽然它读取的数据相对于缓冲区的大小来说小的可怜,但是它依然使得缓冲区大小不再大于等于 highWaterMark 了
此时,可读流内部便再次触发了 _read 方法,而且需要注意到,这次读取的数据大小还是一次完整的 highWaterMark 大小,也就是 1M
我之前一直认为,当缓冲区大小不足一次 highWaterMark 时,可读流会触发 _read 方法去读取数据把缓冲区填满至一次 highWaterMark,现在看来,这是一种错误的认识
上面的打印结果可以明显看到,可读流一共读取了两次,刚好 2M 的大小,但是 readable 事件又触发了两次,读取了两个字节,所以缓冲区最终大小是 2047998,也就是 2 * 1024 * 1000 - 2
当第二次 readable 事件触发后,即使回调函数里也同样读取了一个字节的数据,但是由于缓冲区剩余大小还远远大于一次 highWaterMark 的大小,因此可读流不会再触发 _read 事件了,readable 事件也同样不会在触发
因此,可以认为可读流内部所维护缓冲区的大小范围在(0 <= x < 2 * highWaterMark)
附上生成大体积 txt 文件代码
const fs = require('fs')
const wd = fs.createWriteStream('./test1.txt')
for (let i = 0; i < 1000000; i++) {
wd.write('0123456789')
}