美文网首页我爱编程
nodejs的stream模块

nodejs的stream模块

作者: ElineC | 来源:发表于2018-04-01 20:28 被阅读0次

    Stream 是Node.js中最重要的组件和模式之一,在构建较复杂的系统时,通常将其拆解为功能独立的若干部分。这些部分的接口遵循一定的规范,通过某种方式相连,以共同完成较复杂的任务。nodejs的核心模块,基本上都是stream的的实例,比如process.stdout、http.clientRequest

    什么是流?

    • 流是一组有序的,有起点和终点的字节数据传输手段
    • 它不关心文件的整体内容,只关注是否从文件中读到了数据,以及读到数据之后的处理
    • 流是一个抽象接口,被 Node 中的很多对象所实现。比如HTTP 服务器request和response对象都是流。

    简单的理解,流就是将大块的东西,分小块依次处理。就像你需要从水龙头上接一杯水,那么当你拧开水龙头,水管就会一点点的源源不断的流出来给你。


    image.png

    那么流这种方式在程序当中又有什么优势呢?先看如下代码:

    let fs = require('fs');
    fs.readFile('./1.txt', 'utf8', function(err, data){
        // 1.txt 已经读取完成
        console.log(data);
        fs.writeFile('/2.txt', data); // 将内容写入2.txt中
    });
    

    以上两个方法是实现的功能是将1.txt文件读取到内存当中,再将它写入到2.txt文件中。但是如果文件过大就会出现问题了,内存容易爆掉。那么这里比较合适的方式应该是读写交替进行,也就是使用流的方式读写文件,这样不管文件有多大,都不会一下子耗尽内存,可以安全的执行完。

    如下:

    let fs = require('fs');
    let readStream = fs.createReadStream('./1.txt');
    let writeStream = fs.createWriteStream('./2.txt');
    readStream.on('data', function(chunk) { // 当有数据流出时,写入数据,chunk的类型为Buffer
        writeStream.write(chunk);
    });
    readStream.on('end', function() { // 当没有数据时,关闭数据流
        writeStream.end();
    }); 
    

    流的四种类型

    在nodejs中,有四种stream类型:

    • Readable - 可读的流,用来读取数据 (例如 fs.createReadStream()).
    • Writable - 可写的流,用来写数据 (例如 fs.createWriteStream()).
    • Duplex - 可读写的流(双工流),可读+可写 (例如 net.Socket).
    • Transform - 转换流,在读写过程中可以修改和变换数据的 Duplex 流 (比如 zlib.createDeflate()(数据压缩/解压)).

    1、可读流(Readable streams)

    nodejs中常见的可读流有:fs.createReadStream()、http.IncomingRequest、process.stdin

    可读流createReadStream用法如下:
    // 创建可读流
    let rs = fs.createReadStream(path,[options]);
    
    // 设置编码格式
    rs.setEncoding('utf8');
    
    // 监听open事件,打开文件时触发
    rs.on('open', function () {
        console.log(err);
    });
    
    //流切换到流动模式,数据会被尽可能快的读出
    rs.on("data",function(data){
        console.log(data); //读取到的数据
    });
    
    // 该事件会在读完数据后被触发
    rs.on("end",function(data){
        console.log("数据已经读取完毕");
    });
    
    //如果读取文件出错了,会触发error事件
    rs.on("error",function(err){
        console.log("something is wrong during processing");
    })
    
    //文件关闭触发
    rs.on('close', function () {
         console.log('文件关闭');
    });
    

    1、path读取文件的路径
    2、options

    • flags打开文件要做的操作,默认为'r'
    • encoding默认为null
    • start开始读取的索引位置
    • end结束读取的索引位置(包括结束位置)
    • highWaterMark读取缓存区默认的大小64kb
      如果指定utf8编码highWaterMark要大于3个字节

    2、可写流(Writable streams)

    可写流createReadStream

    实现了stream.Readable接口的对象,将对象数据读取为流数据,当监听data事件后,开始发射数据

     let  fs = require("fs");
    // 创建一个可以写入的流,写入到文件 1.txt 中
    let  ws= fs.createWriteStream('1.txt');
    let  data = '写入流数据';
     
    // 使用 utf8 编码写入数据
    ws.write(data,'UTF8');
     
    // 表明接下来没有数据要被写入 Writable 通过传入可选的 chunk 和 encoding 参数,可以在关闭流之前再写入一段数据 如果传入了可选的 callback 函数,它将作为 'finish' 事件的回调函数
    ws.end("最后写入的数据","utf8",function(){
       console.log(" 我是'finish' 事件的回调函数")
    });
     
    // 在调用了 stream.end() 方法,且缓冲区数据都已经传给底层系统之后, 'finish' 事件将被触发。
    ws.on('finish', function() {
      console.log("写入完成。");
    });
    
    // 写入时发生错误触发
    ws.on('error', function(err){
      console.log(err.stack);
    });
     
    
    // 创建可写流
    let  ws = fs.createWriteStream(path,[options]);
    

    1、path读取文件的路径
    2、options

    • flags打开文件要做的操作,默认为'w'
    • encoding默认为utf8
    • highWaterMark写入缓存区的默认大小16kb

    管道流pipe用法
    将数据的滞留量限制到一个可接受的水平,以使得不同速度的来源和目标不会淹没可用内存。
    linux经典的管道的概念,前者的输出是后者的输入
    pipe是一种最简单直接的方法连接两个stream,内部实现了数据传递的整个过程,在开发的时候不需要关注内部数据的流动

    用法:

    var from = fs.createReadStream('./1.txt');
    var to = fs.createWriteStream('./2.txt');
    from.pipe(to); // 就是从1.txt中读一点就往2.txt中写一点
    

    3、双工流(Duplex streams)

    Duplex实际上就是继承了Readable和Writable。
    有了双工流,我们可以在同一个对象上同时实现可读和可写,就好像同时继承这两个接口。 重要的是双工流的可读性和可写性操作完全独立于彼此。这仅仅是将两个特性组合成一个对象

    const {Duplex} = require('stream');
    const inoutStream = new Duplex({
        write(chunk, encoding, callback) {
            console.log(chunk.toString());
            callback();
        },
        read(size) {
            this.push((++this.index)+'');
            if (this.index > 3) {
                this.push(null);
            }
        }
    });
    
    inoutStream.index = 0;
    process.stdin.pipe(inoutStream).pipe(process.stdout);
    

    最常见的Duplex stream应该就是net.Socket实例了。

    4、转换流(Transform streams)

    转换流的输出是从输入中计算出来的,Transform stream是Duplex stream的特例。也就是说,Transform stream也同时可读可写,它可以用来修改或转换数据。然它跟Duplex stream的区别在于,Transform stream的输出与输入是存在相关性的。你可以认为转换流就是一个函数,这个函数的输入是一个可写流,输出是一个可读流。

    对于转换流,我们不必实现read或write的方法,我们只需要实现一个transform方法,将两者结合起来。它有write方法的意思,我们也可以用它来push数据。

    例如:希望将输入的内容转化成大写在输出出来

    const {Transform} = require('stream');
    
    const upperCase = new Transform({
        transform(chunk, encoding, callback) {
            this.push(chunk.toString().toUpperCase()); // 将输入的内容放入到可读流中
            callback();
        }
    });
    // 希望将输入的内容转化成大写在输出出来
    process.stdin.pipe(upperCase).pipe(process.stdout);
    

    常见的Transform stream包括zlib、crypto,这里有个简单例子:文件的gzip压缩。

    let fs = require('fs');
    let zlib = require('zlib');
    
    let gzip = zlib.createGzip();
    
    // 将1.txt文件的内容,打包压缩成compress.txt.gz
    let inFile = fs.createReadStream('./file/1.txt');
    let outGz = fs.createWriteStream('./file/compress.txt.gz');
    
    inFile .pipe(gzip).pipe(outGz);
    

    相关文章

      网友评论

        本文标题:nodejs的stream模块

        本文链接:https://www.haomeiwen.com/subject/tjkacftx.html