nodejs的stream模块

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

什么是流?

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

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

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

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);
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值