Stream 数据流
说明
Node.js 中的许多组件提供连续输入或可连续处理输入的功能,为了让这些组件行为一致,strean API 提供了一个抽象的接口,提供了常用的方法,以及数据流具体实现时需要使用的属性。数据流分为可读、可写、可读写,所有的流都是 Event Emitter 的实例,都可以主动触发事件。
参考
Node.js 中有以下两种缓存处理方式:
- 第一种是传统的读取文件方式,等到所有数据接收完毕,一次性从缓存中读取,然后处理,优点是简单直接自然,缺点是遇到大文件,需要换很长事件加载。才能进入数据处理步骤
- 第二种是 ‘数据流’ 的方式,每次只读取一小块数据,向了流水一样,每读取一小块数据,就触发一个事件,在这个事件中可以进行数据的处理,即在数据没有完全接受完成时,就开始处理它,可以提高程序的性能。
数据流是不同进程之间传递数据的一种方式。管道命令
pipe就起到在不同命令之间连接数据流的作用。‘数据流’ 相当于把较大的数据拆分成为很小的部分,一个命令只要部署了数据流接口,就可以把一个流的输出接到另一个流的输入。数据流通过事件通信,具有readable、writable、drain、data、end、close等事件,既可以读取数据,也可以写入数据,每读取或者写入一段数据,就会触发一次 data 事件。对象模式:使用 Node.js API 创建的流对象只能操作 strings 或者 buffer ,但是,通过一些第三方流的实现,你依然能够处理其他类型的 JavaScript 值(除了 null),这些流被认为是工作在‘对象模式’,在创建流的实例时,可以通过
objectMode选项使流的实例切换到对象模式。
1. 可读数据流
‘可读数据流’ 用来产生数据。它表示数据的来源,只要一个对象提供 ‘可读数据流’, 就表示你可以从其中读取数据
const Readable = require('stream').Readable;
const rb = new Readable();
rb.push('ttt'); // readable.push() 将数据添加到读取队列
rb.push(' kskskss');
rb.push(null); // 提示 readable 数据输入完成
/*
.pipe() 用于连接数据流
process.stdout 标准输出
*/
rb.pipe(process.stdout);
1. 可读流存在两种模式和三种状态
两种模式
flowing流动模式,可读流自动从系统底层读取数据,并通过事件发射器接口尽快将数据提供给应用paused暂停模式,必须显示的调用stream.read()可读流才会释放数据,新建的时候可读流处于暂停态
三种状态,可读流装台可以通过
readable._readableState.flowing获得
null不存在数据消费者,可读流将不会产生数据true数据流处在 流动模式,生成数据false数据流处于 暂停模式,会暂停事件流,但是不会暂停数据生成,数据会堆积到流的内部缓存中
paused暂停模式 转变为flowing流动模式的方法:
- 添加
data事件的监听函数 - 调用
stream.resume()方法 - 调用
stream.pipe()方法将数据发送到下一个可写数据流
转化为流动模式时,如果没有
data事件监听函数,也没有pipe方法的目的地,数据将会遗失
flowing流动模式 转变为paused暂停模式 的方法:
- 如果不存在管道目标,通过调用
stream.pause()实现 - 如果存在管道目标,1)取消
data监听事件;2)调用stream.unpipe()方法移除管道目标
const fs = require('fs');
const readableStream = fs.createReadStream('./test.txt');
let data = '';
let count = 0;
// 初始状态,未产生数据
console.log(readableStream._readableState.flowing); // null
// 开始监听 ‘data’事件, 可读流切换到 flowing 流动模式
readableStream.on('data', function (chunk) {
data += chunk;
count ++;
console.log(readableStream._readableState.flowing); // true 流动模式下状态为 true
if (count === 4) {
// 通过 pause 方法,暂停可读流,切换到 paused 暂停模式
readableStream.pause();
console.log('pause', readableStream._readableState.flowing); // pause false 暂停模式下状态为 false
setTimeout(function () {
// 通过 resume 方法 继续释放 data 事件,切换到 flowing 流动模式
readableStream.resume();
console.log('resume', readableStream._readableState.flowing); // resume true
}, 1000)
}
})
2. 可读流中的事件
close在流或者底层资源关闭后触发,然后不会再有任何事件被触发,不是所有的 可读流都会触发此事件data回调函数中有参数chunk表示数据片段,事件会在流将数据传递给消费者时触发,当流转换为flowing模式时触发end在流中再没有数据可供消费时触发error在底层系统内部出错从而不能产生数据或者流的实现视图传递错误数据时触发readable在流中有数据可供读取时触发,当到达流的尾部时,也会触发此事件
const fs = require('fs');
const readableStream = fs.createReadStream('./test.txt');
let data = '';
let count = 0;
readableStream.on('data', function (chunk) {
data += chunk;
count++;
if (count === 4) {
readableStream.push({name: 'test'}); // 一个错误的操作,可以触发 error 事件
}
})
/*
readable 事件监听系统中有可读的数据,表明了流有了新的动态,要么有新的数据、要么到达尾部
可以使用 readableStream.read() 方法返回可用的数据,如果到达尾部则返回 null
*/
readableStream.on('readable', function () { // 不要与 'data' 事件一起用
while ((chunk = readableStream.read()) !== null) {
data2 += chunk;
}
})
readableStream.on('error', function (err) {
console.log('error', err);
})
readableStream.on('end', function () {
console.log('data', data);
console.log(count);
})
readableStream.on('close', function () {
console.log('close');
})
3. 可读流中的属性和方法
readable属性,表示当前数据流是否是一个可打开的数据流read([size])方法,从内部不缓存区抽出并返回一些数据,如果没有可读的数据返回 null ,size表示字节数setEncoding(encoding)为从可读流读入的数据设置字符编码,可以使得数据流返回指定编码的字符串而不是 Bufferpause()使得flowing模式的流停止触发 ‘data’ 事件,切换为paused模式resume()使得 可读数据流 继续释放 data 事件,由flowing模式 切换为paused模式isPaused()表示是否中断了可读数据流pipe(writableStream,[,options])从可读数据流中读取数据写入可写数据流,是一个自动传送数据的机制,必须在可读数据流上调用,参数为可写数据流,可以采用链式语法,默认可读流end时,可写流也会触发stream.end()结束写入,禁用默认行为需要设置第二个参数{end: false}unpipe([destination])用于将之前通过pipe()方法绑定的流分离,参数是可选填的,表示要分离的流
// 伪代码,有些接口不能一起使用
const fs = require('fs');
const readableStream = fs.createReadStream('./test.txt'); // 可读流
const writableStream = fs.createWriteStream('./output.txt'); // 可写流
let data = '';
let count = 0;
// setEncoding 设置字符编码
readableStream.setEncoding('utf-8');
// pipe 自动读取写入
readableStream.pipe(writableStream);
setTimeout(function () {
console.log('开始分离流');
// unpipe 分离流
readableStream.unpipe(writableStream);
console.log('结束分离流');
}, 10)
console.log('1', readableStream.readable); // true 当前为可打开数据流
readableStream.on('data', function (chunk) {
data += chunk;
count++;
console.log('2', readableStream.readable); // true 当前为可打开数据流
if (count === 4) {
// pause 方法,暂停可读流,切换到 paused 暂停模式
readableStream.pause();
// isPaused 判断是否为 paused 暂停模式
console.log(readableStream.isPaused()) // true
setTimeout(function () {
// resume 方法 继续释放 data 事件,切换到 flowing 流动模式
readableStream.resume();
console.log(readableStream.isPaused()); // false
}, 1000)
}
})
readableStream.on('readable', function () {
let chunk;
// read 从内部不缓存区抽出并返回一些数据
while (null !== (chunk = readableStream.read())) {
console.log(chunk);
}
})
readableStream.on('end', function () {
// console.log('data', data);
console.log(count);
console.log('3', readableStream.readable); // false 当前为不可打开数据流
})
2. 可写数据流
与可读数据流对应,可写数据流 用来接收数据,它允许将某个数据写入目的地,他是数据写入的一种抽象,素有 可写数据流 都实现了
stream.Writable类定义的接口
1. 可写流的属性和方法
writable属性,如果流仍然打开,并且可写,就返回true否则返回falsewrite(chunk[, encoding][, callback])方法,用于向可写数据流写入数据,1)第一个参数表示写入的内容,可以是字符串可以是一个 stream 对象或 buffer 对象,对象模式下可以是任意 javascript 值,2)第二个参数可选,表示编码规范,3)第三个参数是缓冲数据输出时的回调函数;4)write()方法具有返回值,如果要等待drain事件触发才能继续写入数据,将返回false,否则返回truecork()方法,强制将所有写入数据都存放到内存中的缓冲区里,知道调用stream.uncork()或stream.end()方法时,缓冲区里的数据才会被输出,可以用来避免向流中写入大量小数据块导致的性能下降问题uncork输出在cork()方法调用后缓存在内存中的数据setDefaultEncoding(encoding)方法,设置默认编码end([chunk][,encoding][,callback])方法, 结束写入数据,1)第一个参数,最后写入的数据;2)第二个参数,编码规范;3)流结束的回调函数。在调用了stream.end()方法之后,再调用stream.write()方法将会导致错误destroy()摧毁这个流
// 伪代码,有些接口不能一起使用
const fs = require('fs');
const readableStream = fs.createReadStream('./test.txt'); // 可读流
const writableStream = fs.createWriteStream('./output.txt'); // 可写流
let data = '';
let count = 0;
// 设置编码
writableStream.setDefaultEncoding('utf-8');
// cork 强制将所有写入数据都存放到内存中的缓冲区
writableStream.cork();
readableStream.on('data', function (chunk) {
// write 向可写数据流写入数据
const ret = writableStream.write(chunk, 'utf-8', function () {
console.log('finish');
});
count++;
// console.log(ret);
// writable 是否流仍然打开,并且可写
console.log(writableStream.writable); // true
})
readableStream.on('end', function () {
console.log(count);
console.log(writableStream.writable); // true
// destroy 摧毁这个流
writableStream.destroy();
// uncork 释放缓存在内存中的数据
writableStream.uncork();
// end 结束写入数据
writableStream.end('\n结束了!');
console.log(writableStream.writable); // false end() 之后不可写入
})
2. 可写流中的事件
drain(耗尽)事件,writable.write(chunk)返回false以后,当缓存数据全部写入完成,可以继续写入时,会触发drain事件,表示缓存空了。finish与end()方法最后参数回调函数用法一致,都是 end 之后,缓存数据释放,触发finish事件pipe事件, 绑定在可写流上,在可读流触发pipe方法,将数据流导入此可写流上时触发事件,回调函数接受 可读流对象为参数unpipe事件,可读流调用unpipe()方法将可写数据流移出写入的目的地时触发此事件,该事件的回调函数,接受发出该事件的“可读数据流”对象作为参数error事件,如果写入数据或pipe数据时发生错误,就会触发该事件。close事件,事件将在流或其底层资源(比如一个文件)关闭后触发
// 伪代码,有些接口不能一起使用
const fs = require('fs');
const readableStream = fs.createReadStream('./test.txt'); // 可读流
const writableStream = fs.createWriteStream('./output.txt'); // 可写流
let data = '';
let count = 0;
// drain 缓存空了事件
writableStream.on('drain', function () {
console.log('drain');
})
// finish 结束
writableStream.on('finish', function () {
console.log('finish');
})
// pipe 管道流导入
writableStream.on('pipe', function (src) {
console.log('soming piping');
})
// unpipe 管道流中断
writableStream.on('unpipe', function (src) {
console.log('soming unpiping');
})
// error 报错
writableStream.on('error', function (err) {
console.log('error', err);
})
// close 最后关闭
writableStream.on('close', function () {
console.log('close');
})
// 通过管道导入流
readableStream.pipe(writableStream);
setTimeout(function () {
// 分离流,导入停止
readableStream.unpipe(writableStream);
}, 5)
readableStream.on('data', function (chunk) {
const ret = writableStream.write(chunk);
count++;
console.log('ret', ret);
})
readableStream.on('end', function () {
console.log(count);
writableStream.end('\n结束了!');
})
3. Duplex 流(可读、可写)
Duplex 流是同时实现了 Readable 和 Writable 接口的流。
4. Transform (变换)流
也是一种 Duplex 流,他的输入与输出时通过某种方式关联的
本文深入解析Node.js中的流(Stream)概念,包括可读、可写、可读写及变换流的特点与使用方法,探讨流的各种模式与状态,以及事件、属性和方法的应用。
1152

被折叠的 条评论
为什么被折叠?



