node.js中的stream模块

前言

node.js天生异步和事件驱动,比较适合处理I/O相关的任务,所以在处理I/O相关的操作时,可以用stream流。

流的概念

流是一种传输手段,是有顺序的,有起点和终点,它只是一个实现了一些方法的 EventEmitter 。在unix中有一个概念:‘管道’,流在实现的过程中也可被看成是一个管道一样,进行拼接数据,将一个进程的stdout输出看成下一个进程的输入stdin。.pipe()方法是流的精髓,用于数据两端的数据桥接。可以通过 require(‘stream’) 加载 Stream 基类。就会包括以下四种流的类型。

四种基本流的类型:

1、stream.Readable    可读流(需要实现_read方法,关注点在于对数据流读取的细节)
2、stream.Writable     可写流(需要实现_write方法,关注点在于对数据流写入的细节)
3、stream.Duplex        可读/写流(需要实现以上两接口,关注点为以上两接口的细节)
4、stream.Transform  继承自Duplex(需要实现_transform方法,关注点在于对数据块的处理) 

Readable 可读流

有两种模式:flowing mode流动模式和 paused mode暂停模式,流动模式下,数据会自动从来源流出,跟不老泉似的,直到来源的数据耗尽。暂停模式下,你得通过stream.read()主动去要数据,否则不会主动将数据给你。

可读流在创建时都是暂停模式。暂停模式和流动模式可以互相转换。

要从暂停模式切换到流动模式,有下面三种办法:

  1. 给“data”事件关联了一个处理器
  2. 显式调用resume()
  3. 调用pipe()将可读流桥接到一个可写流上
  4. 要从流动模式切换到暂停模式,有两种途径:
  5. 如果这个可读的流没有桥接可写流组成管道,直接调用pause()
  6. 如果这个可读的流与若干可写流组成了管道,需要移除与“data”事件关联的所有处理器,并且调用unpipe()方法断开所有管道。

需要注意的是,出于向后兼容的原因,移除“data”事件的处理器,可读流并不会自动从流动模式转换到暂停模式;还有,对于已组成管道的可读流,调用pause也不能保证这个流会转换到暂停模式。

Readable流的一些常见实例如下:

  1. 客户端的HTTP响应
  2. 服务端的HTTP请求
  3. fs读取流
  4. zlib流
  5. crypto(加密)流
  6. TCP套接字
  7. 子进程的stdout和stderr
  8. process.stdin

Readable流提供了以下事件:

  1. readable:在数据块可以从流中读取的时候发出。它对应的处理器没有参数,可以在处理器里调用read([size])方法读取数据。
  2. data:有数据可读时发出。它对应的处理器有一个参数,代表数据。如果你只想快快地读取一个流的数据,给data关联一个处理器是最方便的办法。处理器的参数是Buffer对象,如果你调用了Readable的setEncoding(encoding)方法,处理器的参数就是String对象。
  3. end:当数据被读完时发出。对应的处理器没有参数。
  4. close:当底层的资源,如文件,已关闭时发出。不是所有的Readable流都会发出这个事件。对应的处理器没有参数。
  5. error:当在接收数据中出现错误时发出。对应的处理器参数是Error的实例,它的message属性描述了错误原因,stack属性保存了发生错误时的堆栈信息。
var fs=require('fs');
var path=require('path');
var txt = path.resolve('./text.txt');//text.txt里面的内容12345678901234567890
//指定开始位置,结束位置读取文件
// 创建一个可读流
var readable=fs.createReadStream(txt,{
    encoding:'utf8',//不传默认为buffer,显示为字符串
	highWaterMark:6,//缓冲区大小
	start:6,//开始索引值
	end:12//结束索引值
});
readable.on('open',function(fd){
	console.log('打开文件成功,句柄:'+fd); // 打开文件成功,句柄:3
});
readable.on('data',function(data){
    console.log(data)
    readable.pause();//暂停读取和发射data事件
    setTimeout(function(){
        readable.resume();//恢复读取并触发data事件
    },2000);
});
readable.on('end',function(){
	console.log('读取结束');  //读取结束
});
readable.on('close',()=>{
	console.log('读取关闭');  //读取关闭
});
readable.on('error',function(err){
	console.log('读取异常,'+err); //当出现异常时触发
})

Writable streams可写流

Writable:可写流的例子包括了:

  1. HTTP requests, on the client 客户端请求
  2. HTTP responses, on the server 服务器响应
  3. fs write streams 文件
  4. zlib streams 压缩
  5. crypto streams 加密
  6. TCP sockets TCP服务器
  7. child process stdin 子进程标准输入
  8. process.stdout, process.stderr 标准输出,错误输出

注意:每次写入文件的时候并不是直接写入文件的,而是先写入缓冲区,待缓冲区写满后再写入到文件里面。缓存区的大小就是highWaterMark,默认值是16K。

let fs = require('fs');
let ws = fs.createWriteStream('./txt.txt',{
   flags:'w',
   mode:0o666,
   start:3,
   highWaterMark:3//默认是16K
});
let flag = ws.write('1');
console.log(flag);//true
flag =ws.write('2');
console.log(flag);//true
flag =ws.write('3');
console.log(flag);//false
flag =ws.write('4');
console.log(flag);//false
  1. 如果缓存区已满 ,返回false,如果缓存区未满,返回true
  2. 如果能接着写,返回true,如果不能接着写,返回false
  3. 按理说如果返回了false,就不能再往里面写了,但是如果你真写了,如果也不会丢失,会缓存在内存里。等缓存区清空之后再从内存里读出来

自定义可写流

为了实现可写流,我们需要使用流模块中的Writable构造函数。

创建一个writable流:

  1. chunk代表写进去的数据
  2. encoding字符编码
  3. callback是一个回调函数
var stream = require('stream');
var util = require('util');
util.inherits(Writer, stream.Writable);
let stock = [];
function Writer(opt) {
    stream.Writable.call(this, opt);
}
Writer.prototype._write = function(chunk, encoding, callback) {
    setTimeout(()=>{
        stock.push(chunk.toString('utf8'));
        console.log("增加: " + chunk);
        callback();
    },500)
};
var w = new Writer();
for (var i=1; i<=5; i++){
    w.write("项目:" + i, 'utf8');
}
w.end("结束写入",function(){
    console.log(stock);
});

Duplex streams可读写的流(双工流)

Duplex 流是同时实现了 Readable 和 Writable 接口的流 
双工流的可读性和可写性操作完全独立于彼此,这仅仅是将两个特性组合成一个对象

Duplex 流的实例包括了:

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

Transform streams转换流

转换流(Transform streams) 是一种 Duplex 流。它的输出与输入是通过某种方式关联的。和所有 Duplex 流一样,变换流同时实现了 Readable 和 Writable 接口

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

变换流的实例包括:

  1. zlib streams
  2. crypto streams
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);

对象流:

默认情况下,流处理的数据是Buffer/String类型的值。有一个objectMode标志,我们可以设置它让流可以接受任何JavaScript对象。

const {Transform} = require('stream');
let fs = require('fs');
let rs = fs.createReadStream('./users.json');
rs.setEncoding('utf8');
let toJson = Transform({
    readableObjectMode: true,
    transform(chunk, encoding, callback) {
        this.push(JSON.parse(chunk));
        callback();
    }
});
let jsonOut = Transform({
    writableObjectMode: true,
    transform(chunk, encoding, callback) {
        console.log(chunk);
        callback();
    }
});
rs.pipe(toJson).pipe(jsonOut);

pipe管道+链式流

前者的输出是后者的输入,pipe是一种最简单直接的方法连接两个stream,内部实现了数据传递的整个过程,在开发的时候不需要关注内部数据的流动。

  1. 这个方法从可读流拉取所有数据, 并将数据写入到提供的目标中
  2. 自动管理流量,将数据的滞留量限制到一个可接受的水平,以使得不同速度的来源和目标不会淹没可用内存
  3. 默认情况下,当源数据流触发 end的时候调用end(),所以写入数据的目标不可再写。传 { end:false }作为options,可以保持目标流打开状态

下面这段代码中,服务器每收到一次请求,就会先把data.txt读入到内存中,然后再从内存取出返回给客户端。尴尬的是,如果data.txt非常的大,而每次请求都需要先把它全部存到内存,再全部取出,不仅会消耗服务器的内存,也可能造成用户等待时间过长。


var http = require('http');
var fs = require('fs')
var server = http.createServer(function (req, res) {
    fs.readFile(__dirname + '/data.txt', function (err, dat
        res.end(data);
    });
});
server.listen(8000);

HTTP请求中的request对象和response对象都是流对象,于是我们可以换一种方法:


var http = require('http');
var fs = require('fs');
 
var server = http.createServer(function (req, res) {
    let stream = fs.createReadStream(__dirname + '/data.txt');//创造可读流
    stream.pipe(res);//将可读流写入response
});
server.listen(8000);

会在脚本的运行同级目录下压缩成一个text.gz压缩包

var fs=require('fs');
var zlib=require('zlib');
var path=require('path');
var file1=path.resolve('./text.txt');
var file3=path.resolve('./text.gz');
//压缩文件
var readable1=fs.createReadStream(file1);
var writeable=fs.createWriteStream(file3);
readable1
.pipe(zlib.createGzip())
.pipe(writeable);
 
console.log('压缩文件完成');

解压压缩文件

var fs = require("fs");
var zlib = require('zlib');

// 解压 input.txt.gz 文件为 input.txt
fs.createReadStream('./text.gz')
  .pipe(zlib.createGunzip())
  .pipe(fs.createWriteStream('text.txt'));
  
console.log("文件解压完成。");

pipe的几种应用场景

简单stream进行的pipe使用

var path = require('path');
var fs = require("fs");
var file1=path.resolve('./test.txt');
var a = process.stdin.pipe(process.stdout)
var b = process.stdin.pipe(fs.createWriteStream(file1))
var c = fs.createReadStream(file1).pipe(process.stdin)
console.log(a,b,c)
// 输出
// Socket {connecting: false, _hadError: false, _handle: Pipe, _parent: null, _host: null, …}
// WriteStream {_writableState: WritableState, writable: true, domain: null, _events: Object, _eventsCount: 5, …}
// Socket {connecting: false, _hadError: false, _handle: Pipe, _parent: null, _host: null, …}

  剩下的几种方法参考此链接:https://blog.csdn.net/vieri_32/article/details/48376547

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值