Appearance
前言
最近webpack的编写需要一些流相关的知识来处理文件,记录一下nodejs中流和浏览器中的流。 流可以理解成一个数据的接口,就像一个水龙头一样,会不断的流出数据,当然也可以操作从另一端注入数据。流中的数据是什么,(buffer和string)。buffer后面将
流用来解决通过极小的内存来读取较大的文件,
stream类型
Readable, 可读的流,不能被写入 Writeable 可写的流, 不能读 Duplex 可读写的流, 同时具备可读和可写,可单工也可以双工,内部的两个缓存区是独立没有关系 Transform 流入的数据经过加工后流出。是Duplex高级实现,打通了内部的两个缓存区,通过_transform实现两个缓存区沟通,可以理解为readable.pipe(someTransform).pipe(writeable)
流继承了EventEmit,有data,end(没有可读时触发), error, finish(没有可写的内容时触发)等事件,通过on('xxx', cb) 回调
上面其实有疑问,一个可读的流中的数据是怎么来的, 一个可写的流为什么不能读,光写也没有意义?
流怎么来
- 创建一个文件读取流
js
const fsStream = fs.createReadStream('xx.txt');
fsStream.setEncoding('utf-8') // 给流设置的编码,不设置的话流每次触发事件间隔会不一样
fs.on('data', (chunk) => {
// 设置编码后,每次chunk就会返回一个utf-8编码的chunk,返回多位二进制
// chunk 是一个buffer 或者 string
data += chunk
})- 一个可写的流
ts
const readStream = fs.createWriteStream('xxx.txt');
readStream.write('data', 'utf-8');
readStream.write('data', 'utf-8'); // 如果返回false 表示不能缓存区满,这时需要监听drwin事件来进行下一次写入
readStream.on('finish', () => {
console.log('finish')
})
readStream.end()stream有两种mode,一个是buffer mode(nodejs创建的都是这个模式,其中的highWaterMark代表的字节数),一个是objectMode(除了上面的都是object mode,highWaterMark是object数量) 流内部会有一个缓存区,stream._writableState.getBuffer() 或者 stream._readable.getBuffer()。当缓存区满的时候,流会停止写入或者流出。直到被消耗。当然缓存区是由大小限制的,highWaterMark代表流的阈值()
因为阈值的存在,如果输入的速度大于输出的速度,缓存区就有可能满。 3. 自行实现一个可读流和可写流
ts
class MyReadStream extends Readable {
constructor() {
super()
this._iterator = (*function() {
yield 1;
yield 2;
yield 3;
})()
}
// 可读流的核心在_read, 支持手动MyReadStream.read() 和 自动模式on('data', cb)
// 两种模式都会调用内部的_read方法
_read() {
const { value, done } = this._iterator.next();
if(!done) {
this.push(String(value))
} else {
// push null表示可读流结束
this.push(null)
}
}
}
let file = '';
class MyWriteStream extends Readable {
constructor() {
super()
this._iterator = (*function() {
yield 1;
yield 2;
yield 3;
})()
}
// 可写流在每次调用手动write时都会调用内部的_write
_write(str) {
// string 和 buffer 都有toString方法
file += str.toString()
//
return false
}
}上面例子中,比较难理解的是可写流的写入,会发现可写流中写入都是同步的,应该不存在缓存满的情况,实际上_write 调用方式是每次当我们调用可写流的 write() 方法时,它会向缓冲区写入数据,当达到阈值时,便会调用 _write() 方法将数据新增到文件中。并不是同步调用的。
对于可读流如下 开始执行时,可读流内部缓存中没有数据,会先读取highWaterMark(默认64kb,可指定)个字节的数据到内部缓存中。 readable读取之后,如果内部缓存区中的字节少于highWaterMark个字节的时候,会主动再读取highWaterMark字节的数据到内部缓存中。 如果readable读取后,内部缓存中没有数据了,会主动再读取highWaterMark字节的数据到内部缓存中。这是2中的一种特殊情形。 当readable触发时,如果缓存中的数据不够读取,则会读取最邻近的2的高次幂个字节放入缓存中。例如本次readable要读取5个字节,但是缓存中字节小于5个,则会读取2^3个字节到缓存中。
什么时候会调用readable将内部缓存中的数据取出?
当可读流内部缓存区中字节数为0的时候,包括开始执行时的0和后期读取时被清空两种情形,会触发一次readable事件。 如果readable执行时,当前缓存区中的数据不够读取,则会在从文件读取数据到缓存(对应读取文件时机的第4条)后,再次触发readable事件。 当到达可读流底部的时候,也就是文件已经被全部读到缓存中,且调用过一次readable事件后,会再次触发readable事件。(这是一次校验清理缓存的过程,也是上面例子最后输出了null的原因)
highWaterMark 和 size 理解
highWaterMark指的是缓存区大小,read(size) 也是指的写入数据的大小。在object mode: false 的模式很好理解,highWaterMark单位是字节表示缓存区有多少字节, read(size) 表示一次读取多少字节。但是在object mode : true 情况缓存区是什么。
object mode: true 情况下缓存区是保存时具体的对象,不再是buffer节点。 每次read也只会读出一个值出来
