快速认识Node.js中的Stream

660 阅读4分钟

StreamNode.js中处理文件、HTTP请求等功能的重要依赖,接下来我们就了解一下什么是Stream?以及Stream的常见用法。

开始Stream之前,我们先了解一下流的相关概念,将有助于我们理解Stream。在服务端处理文件以及HTTP请求时,我们经常会用流的方法,来实现文件的读写,数据的传输和处理。


0.1 流数据 来自wiki的定义 Wiki

流数据是由不同来源连续生成的数据。
流是一组有序的,有起点和终点的字节数据传输手段。
这些数据应该使用流处理技术逐步处理,而无需访问所有数据。
另外,应该考虑到在数据中可能发生概念漂移,这意味着流的属性可能随时间而改变。

**0.2 数据流 **

 数据流是一系列数字编码的相干信号(数据包或数据包),用于传输或接收正在传输过程中的信息。
 一般我们将数据转换为二进制或者其它base格式进行传输。

1. 流的定义

流是用于处理Node.js中的流数据的抽象接口。stream模块提供了很多API可供实现stream的接口。Node.js中提供了很多stream的实例,如HTTP serverprocess.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 可读流的两种模式

  • flowingpaused

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]);
  1. 监听data事件,流切换到流动模式,数据会被尽可能快的读出
rs.on('data', function (data) {
    console.log(data);
});
  1. 监听end事件,该事件会在读完数据后被触发
rs.on('end', function () {
    console.log('读取完成');
});
  1. 监听error事件
rs.on('error', function (err) {
    console.log(err);
});
  1. 监听open事件
rs.on('open', function () {
    console.log(err);
});
  1. 监听close事件
rs.on('close', function () {
    console.log(err);
});
  1. 设置编码,与指定{encoding:'utf8'}效果相同,设置编码
rs.setEncoding('utf8');
  1. 暂停和恢复触发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);

将数据的滞留量限制到一个可接受的水平,以使得不同速度的来源和目标不会淹没可用内存。