平时会用到很多流,request,response,file, socket 都是stream。那么stream是怎么实现的呢,它解决了什么问题呢。
众所周知的生产消费问题,消费者消费生产者生产的数据,如果他们两个直接交互,由于生产者的生产速度和消费者的消费速度不固定,很容易产生摩擦(请自行脑补一下)。于是有了缓冲池,生产者向缓冲池注入,消费者从缓冲池抽取。虽然也会有缓冲池水位过高不能再注入的情况,但是问题还是简化了不少。stream就是这个解决方案的实现。
stream 的流动状态
_readableState.flow = null,暂时没有消费者过来
_readableState.flow = false,主动触发了 .pause()
_readableState.flow = true,流动模式
消费者怎么抽取数据
当readStream 监听了readable event 之后,开始主动索取数据read(). 每次数据push到缓冲池,都会触发readable事件。可以通过 _readableState.buffer 来查看缓存池到底缓存了多少,buffer 大小也是有上限的,默认设置为 16kb,也就是 16384 个字节长度,它最大可设置为 8Mb。 各个stream之间的流动用pipe函数。
const myReadable = new MyReadable(dataSource);
myReadable.setEncoding('utf8');
myReadable.on('readable', () => {
console.log(myReadable._readableState.buffer.length);
let chunk;
while (null !== (chunk = myReadable.read())) {
console.log(`Received ${chunk.length} bytes of data.`);
}
});
import fs from 'fs'
// 读取流
let data = ''
const readStream = fs.createReadStream('shell.sh', {
encoding: 'utf-8'
})
readStream.on('data', (chunk) => {
data += chunk
})
readStream.on('end', () => {
console.log(data)
})
readStream.on('error', (error) => {
console.log(error)
})
// 写入流
const writeStream = fs.createWriteStream('test.txt', {
encoding: 'utf-8'
})
writeStream.write("我是测试stream", 'utf-8')
writeStream.on('finish', () => {
console.log('写入完成')
})
// 管道流
writeStream.write('')
readStream.pipe(writeStream).on('finish', () => {
console.log('管道流完成')
})
Duplex Stream
一般的stream都是readable 或者 writable stream,在上述生产者,缓冲池,消费者三个概念中占用两个。
duplex是双工的stream,既能读也能写。他的输入和输出是分开的。输入是单独的 write stream, 输出是 read stream。缓冲池也是两个。
Transform Stream
Transform Stream 集成了 Duplex Stream,它同样具备 Readable 和 Writable 的能力,只不过它的输入和输出是存在相互关联的,中间做了一次转换处理。常见的处理有 Gzip 压缩、解压等。
Transform 的处理就是通过 _transform 函数将 Duplex 的 Readable 连接到 Writable,由于 Readable 的生产效率与 Writable 的消费效率是一样的,所以这里 Transform 内部不存在「背压」问题,背压问题的源头是外部的生产者和消费者速度差造成的。