Stream是Node.js中处理文件、HTTP请求等功能的重要依赖,接下来我们就了解一下什么是Stream?以及Stream的常见用法。
开始
Stream之前,我们先了解一下流的相关概念,将有助于我们理解Stream。在服务端处理文件以及HTTP请求时,我们经常会用流的方法,来实现文件的读写,数据的传输和处理。
0.1 流数据 来自wiki的定义 Wiki
流数据是由不同来源连续生成的数据。
流是一组有序的,有起点和终点的字节数据传输手段。
这些数据应该使用流处理技术逐步处理,而无需访问所有数据。
另外,应该考虑到在数据中可能发生概念漂移,这意味着流的属性可能随时间而改变。
**0.2 数据流 **
数据流是一系列数字编码的相干信号(数据包或数据包),用于传输或接收正在传输过程中的信息。
一般我们将数据转换为二进制或者其它base格式进行传输。
1. 流的定义
流是用于处理Node.js中的流数据的抽象接口。stream模块提供了很多API可供实现stream的接口。Node.js中提供了很多stream的实例,如HTTP server和process.stdout。在它们中能充分体验到stream的应用。
1.1 引用stream模块:
const stream = require('stream');
2. 流的分类
Node.js中有4中基本流的类型:
- 可读流
Readable:可以读的流fs.createReadStream() - 可写流
Writable: 可以写的流fs.createWriteStream() - 双工流
Duplex: 既可读,又可以写net.Socket - 转换流
Transform: 在写入和读取数据时可以修改或转换数据的双工数据流,zlib.createDeflate()
3. 流中的数据模式
- 二进制模式,每个分块都是buffer或者string对象
- 对象模式,流内部处理的是一系列普通对象
4. 缓存区Buffer
Writable 和 Readable 流都会将数据存储到内部的缓冲器(buffer)中。
这些缓冲器可以 通过相应的 writable._writableState.getBuffer() 或
readable._readableState.buffer 来获取。
当内部可读缓冲器的大小达到highWaterMark指定的阈值时,
流会暂停从底层资源读取数据,直到当前缓冲器的数据被消费 (也就是说,
流会在内部停止调用 readable._read()来填充可读缓冲器)。
可写流通过反复调用 writable.write(chunk)
方法将数据放到缓冲器。当内部可写缓冲器的总大小小于highWaterMark指定的阈
值时, 调用 writable.write() 将返回true。一旦内部缓冲器的大小达到或超过
highWaterMark ,调用writable.write() 将返回 false 。
5. 可读流
5.1 可读流的两种模式
flowing和paused
5.2 可读流的三种状态
- readable._readableState.flowing = null
- readable._readableState.flowing = false
- readable._readableState.flowing = true
5.3 可读流的常用方法
可读流,实现了stream.Readable接口的对象,将对象数据读取为流数据,当监听data事件后,开始发射数据
5.3.1 创建可读流
var rs = fs.createReadStream(path,[options]);
- 监听data事件,流切换到流动模式,数据会被尽可能快的读出
rs.on('data', function (data) {
console.log(data);
});
- 监听end事件,该事件会在读完数据后被触发
rs.on('end', function () {
console.log('读取完成');
});
- 监听error事件
rs.on('error', function (err) {
console.log(err);
});
- 监听open事件
rs.on('open', function () {
console.log(err);
});
- 监听close事件
rs.on('close', function () {
console.log(err);
});
- 设置编码,与指定{encoding:'utf8'}效果相同,设置编码
rs.setEncoding('utf8');
- 暂停和恢复触发data,通过pause()方法和resume()方法
rs.on('data', function (data) {
rs.pause();
console.log(data);
});
setTimeout(function () {
rs.resume();
},2000);
6. 可写流
实现了stream.Writable接口的对象来将流数据写入到对象中
fs.createWriteStream = function(path, options) {
return new WriteStream(path, options);
};
util.inherits(WriteStream, Writable);
6.1 创建可写流
var ws = fs.createWriteStream(path,[options]);
path写入的文件路径
options
flags打开文件要做的操作,默认为'w'
encoding默认为utf8
highWaterMark写入缓存区的默认大小16kb
6.2 write方法
ws.write(chunk,[encoding],[callback]);
chunk写入的数据buffer/string encoding编码格式chunk为字符串时有用,可选 callback 写入成功后的回调 返回值为布尔值,系统缓存区满时为false,未满时为true
6.3 end方法
ws.end(chunk,[encoding],[callback]);
表明接下来没有数据要被写入 Writable 通过传入可选的 chunk 和 encoding 参数,可以在关闭流之前再写入一段数据 如果传入了可选的 callback 函数,它将作为 'finish' 事件的回调函数
6.4 drain方法
当一个流不处在 drain 的状态, 对 write() 的调用会缓存数据块, 并且返回 false。 一旦所有当前所有缓存的数据块都排空了(被操作系统接受来进行输出), 那么 'drain' 事件就会被触发
let fs = require('fs');
let ws = fs.createWriteStream('./2.txt',{
flags:'w',
encoding:'utf8',
highWaterMark:3
});
let i = 10;
function write(){
let flag = true;
while(i&&flag){
flag = ws.write("1");
i--;
console.log(flag);
}
}
write();
ws.on('drain',()=>{
console.log("drain");
write();
});
6.4 finish方法
在调用了 stream.end() 方法,且缓冲区数据都已经传给底层系统之后, 'finish' 事件将被触发。
var writer = fs.createWriteStream('./2.txt');
for (let i = 0; i < 100; i++) {
writer.write(`hello, ${i}!\n`);
}
writer.end('结束\n');
writer.on('finish', () => {
console.error('所有的写入已经完成!');
});
7 Pipe 方法
7.1 pipe方法的原理
var fs = require('fs');
var ws = fs.createWriteStream('./2.txt');
var rs = fs.createReadStream('./1.txt');
rs.on('data', function (data) {
var flag = ws.write(data);
if(!flag){
rs.pause();
}
});
ws.on('drain', function () {
rs.resume();
});
rs.on('end', function () {
ws.end();
});
readStream.pipe(writeStream);
var from = fs.createReadStream('./1.txt');
var to = fs.createWriteStream('./2.txt');
from.pipe(to);
将数据的滞留量限制到一个可接受的水平,以使得不同速度的来源和目标不会淹没可用内存。