一 什么是stream
- 流(stream)是 Node.js 中处理流式数据的抽象接口。 stream 模块用于构建实现了流接口的对象。Node.js 提供了多种流对象。 例如,HTTP 服务器的请求和 process.stdout 都是流的实例。
- 流的好处
非阻塞式的数据处理方式可提升效率;chunk 数据分块可以节省内存;管道可提高扩展性,方便组合;
//使用流模块
const stream=require('stream')
二 Node.js 中有四种基本的流类型:
- Writable - 可写入数据的流(例如 fs.createWriteStream())。
- Readable - 可读取数据的流(例如 fs.createReadStream())。
- Duplex - 可读又可写的流(例如 net.Socket)。
- Transform - 在读写过程中可以修改或转换数据的。
三 Writable Stream中'drain' 事件
- 考虑如下场景,stream1中数据通过管道流向stream2中,由于stream1流的太快,stream2来不及写,此时会导致数据积压,此时stream2.write(chunk)会返回false。直到可以继续向stream2中写入数据时会触发 'drain' 事件,也可以理解stream2数据写完了,已经干涸了,可以继续写数据了。因此,当 writable.write(chunk) 的返回值是 false时,应该停止 write 数据,转而监听 wriable 的 drain 事件,之后再 write
//studyDrainEvent.js
const fs=require('fs');
function writeOneMillionTimes(writer, data) {
let i = 1000000;
write();
function write() {
let ok = true;
do {
i--;
if (i === 0) {
// 最后一次写入。
writer.write(data);
} else {
// 检查是否可以继续写入。
// 不要传入回调,因为写入还没有结束。
ok = writer.write(data);
if(ok===false){
console.log('不能再写了');
}
}
} while (i > 0 && ok);
if (i > 0) {
// 被提前中止。
// 当触发 'drain' 事件时继续写入。
writer.once('drain', ()=>{
console.log('干涸了');
write();
});
}
}
}
const writer=fs.createWriteStream('./bigfile.txt');
writeOneMillionTimes(writer, 'hello world\n');
四 Writable Stream中finish事件
调用stream.end()之后,而且缓冲区数据都已经传给底层操作系统之后,触发finish事件。
五 Readable Stream
默认处于paused态,添加data事件监听,就变为flowing态,删掉data事件监听,就变为paused态。pause()可以将其变为paused态。resume()可以将其变为flowing态
六 自定义stream
//创建一个Writable Stream
const {Writable}=require('stream')
const outStream=new Writable({
write(chunk,encoding,callback){
console.log(chunk.toString());
callback();
}
})
//用户输入
// process.stdin.pipe(outStream);
process.stdin.on('data',(chunk)=>{
outStream.write(chunk);
})
2.
const {Readable}=require('stream');
const inStream=new Readable({
read(chunk,encoding,callback){
this.push(String.fromCharCode(this.currentCharCode++));
if(this.currentCharCode>90){
this.push('\n');
this.push(null);
}
}
});
inStream.currentCharCode=65;
// inStream.pipe(process.stdout);
inStream.on('data',(chunk)=>{
process.stdout.write(chunk);
})
七 缓冲
可写流和可读流都会在内部的缓冲器中存储数据,可以分别使用的 writable.writableBuffer 或 readable.readableBuffer 来获取。 当调用 stream.push(chunk) 时,数据会被缓冲在可读流中。 如果流的消费者没有调用 stream.read(),则数据会保留在内部队列中直到被消费。 一旦内部的可读缓冲的总大小达到 highWaterMark 指定的阈值时,流会暂时停止从底层资源读取数据,直到当前缓冲的数据被消费 (也就是说,流会停止调用内部的用于填充可读缓冲的 readable._read())。 highWaterMark 就是高水位线,表明了流的缓冲的容量 一旦内部的可读缓冲的总大小达到 highWaterMark 指定的阈值时,流会暂时停止读取数据,直到当前缓冲的数据被消费。 因为 Duplex 和 Transform 都是可读又可写的,所以它们各自维护着两个相互独立的内部缓冲器用于读取和写入, 这使得它们在维护数据流时,读取和写入两边可以各自独立地运作。 例如,net.Socket 实例是 Duplex 流,它的可读端可以消费从 socket 接收的数据,而可写端则可以将数据写入到 socket。 因为数据写入到 socket 的速度可能比接收数据的速度快或者慢,所以读写两端应该独立地进行操作(或缓冲)。
八 transform流举例
const fs = require("fs");
const zlib = require("zlib");
const file ='./bigfile.txt';
fs.createReadStream(file)
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream(file + ".gz"));