Node.js中stream模块

1,301 阅读3分钟

一 什么是stream

  1. 流(stream)是 Node.js 中处理流式数据的抽象接口。 stream 模块用于构建实现了流接口的对象。Node.js 提供了多种流对象。 例如,HTTP 服务器的请求和 process.stdout 都是流的实例。
  2. 流的好处
    非阻塞式的数据处理方式可提升效率;chunk 数据分块可以节省内存;管道可提高扩展性,方便组合;
    //使用流模块
    const stream=require('stream')

二 Node.js 中有四种基本的流类型:

  1. Writable - 可写入数据的流(例如 fs.createWriteStream())。
  2. Readable - 可读取数据的流(例如 fs.createReadStream())。
  3. Duplex - 可读又可写的流(例如 net.Socket)。
  4. Transform - 在读写过程中可以修改或转换数据的。

三 Writable Stream中'drain' 事件

  1. 考虑如下场景,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"));