微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

浅谈node.js中的stream(流)

前言

什么是流呢?看字面意思,我们可能会想起生活中的水流,电流。
但是流不是水也不是电,它只是描述水和电的流动;所以说流是抽象的。
在node.js中流是一个抽象接口,它不关心文件内容,只关注是否从文件中读到了数据,以及读到数据之后的处理,接着看:

1.流的概念

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

看看官网的介绍:

编程之家

这里说了“所有的流都是EventEmitter的实例” 所以流继承了EventEmitter类。再来看流的类型:

2.流的类型

3.流的数据模式

流中的数据有两种模式,二进制模式和对象模式.

  1. 二进制模式,每个分块都是buffer或者string对象.
  2. 对象模式,流内部处理的是一系列普通对象.
注意:
所有使用 Node.js API 创建的流对象都只能操作 strings 和 Buffer对象。但是,通过一些第三方流的实现,你依然能够处理其它类型的 JavaScript 值 (除了 null,它在流处理中有特殊意义)。 这些流被认为是工作在 “对象模式”(object mode)。 在创建流的实例时,可以通过 objectMode 选项使流的实例切换到对象模式。试图将已经存在的流切换到对象模式是不安全的。
说了那么多,现在开始写流:

4.可读流(createReadStream)

4.1 创建可读流

编程之家

这里说说流程:

  • 首先可读流会打开文件,触发open事件
  • 接着开始读取,疯狂的触发data事件
  • 然后读完了,触发end事件
  • 最后因为设置了autoClose为true,自动关闭文件触发close事件

可以看到,data事件不断的被触发,当我们想读一下停一下时怎么办呢?
这里就需要聊聊可读流的两种模式:

4.2 可读流的两种模式

  • 可读流事实上工作在下面两种模式之一:flowing 和 paused
  • 在 flowing 模式下, 可读流自动从系统底层读取数据,并通过 EventEmitter 接口的事件尽快将数据提供给应用。
  • 在 paused 模式下,必须显式调用 stream.read() 方法来从流中读取数据片段。

初始工作模式为 paused 的 Readable 流,可以通过下面三种途径切换到 flowing 模式:

1. 监听 'data' 事件
2. 调用 stream.resume() 方法

编程之家

3.调用 stream.pipe() 方法将数据发送到 Writable
注意:
如果 Readable 切换到 flowing 模式,且没有消费者处理流中的数据,这些数据将会丢失。 比如, 调用了 readable.resume() 方法却没有监听 'data' 事件,或是取消了 'data' 事件监听,就有可能出现这种情况

在 paused 模式下,必须显式调用 stream.read() 方法来从流中读取数据片段。
在可读流'readable'事件里我们就必须调用stream.read()方法

4.3 可读流'readable'事件

这里需要明白三点:
先创建一个1.txt

编程之家

1.当我只要创建一个流 就会先把缓存区 填满,等待着你自己消费
2.当你消费小于 最高水位线时 会自动添加highWaterMark这么多数据

编程之家

3.如果当前缓存区被清空后会再次触发readable事件

编程之家

4.4 readable原理图

  • 用Readable创建对象readable后,便得到了一个可读流。
  • 如果实现_read方法,就将流连接到一个底层数据源。
  • 流通过调用_read向底层请求数据,底层再调用流的push方法将需要的数据传递过来。
  • 当readable连接了数据源后,下游便可以调用readable.read(n)向流请求数据,同时监听readable的data事件来接收取到的数据。

编程之家

5.可写流(createWriteStream)

5.1 创建可写流

5.1.1 write方法

编程之家

5.1.2 end方法

表明接下来没有数据要被写入 Writable 通过传入可选的 chunk 和 encoding 参数,可以在关闭流之前再写入一段数据 如果传入了可选的 callback 函数,它将作为 'finish' 事件的回调函数

编程之家

5.1.3 drain方法

drain事件的触发条件,必须满足两个条件:
1.当前缓存区满了,不能再写了
2.缓存区满了后被清空了,才会触发drain事件

编程之家

6.pipe方法(管道)

我们在开发中可能会遇到,要把可读流读出的数据需要放到可写流中去写入到文件里面,这时就可以用pipe方法

6.1 pipe的原理

pipe方法的原理很简单,就是读一点,写一点,上代码

let fs = require('fs');
let ws = fs.createWriteStream('./2.txt');
let rs = fs.createReadStream('./1.txt');
rs.on('data',data => {
    var flag = ws.write(data);
    if(!flag)
    rs.pause();
});
ws.on('drain',() => {
    rs.resume();
});
rs.on('end',() => {
    ws.end();
});

6.2 pipe的用法

let from = fs.createReadStream('./1.txt');
let to = fs.createWriteStream('./2.txt');
from.pipe(to);

6.3 unpipe方法

readable.unpipe()方法将之前通过stream.pipe()方法绑定的流分离

let fs = require('fs');
let from = fs.createReadStream('./1.txt');
let to = fs.createWriteStream('./2.txt');
from.pipe(to);
setTimeout(() => {
    console.log('关闭向2.txt的写入');
    from.unpipe(writable);
    console.log('手动关闭可写流');
    to.end();
},1000);

7.自定义

我们可以引入stream模块,想实现什么流 就继承这个流。

7.1 自定义可读流

  • 我们可以直接把供使用的数据push出去。
  • 当push一个null对象就意味着我们想发出信号——这个流没有更多数据了

编程之家

7.2 自定义可写流

为了实现可写流,我们需要使用流模块中的Writable构造函数。 我们只需给Writable构造函数传递一些选项并创建一个对象。唯一需要的选项是write函数,该函数揭露数据块要往哪里写。

编程之家

7.3 实现双工流(duplex)

有了双工流,我们可以在同一个对象上同时实现可读和可写,就好像同时继承这两个接口。 重要的是双工流的可读性和可写性操作完全独立于彼此。
net中的Socket就是一个duplex双工流

编程之家

8.结束语

说到这里,我想大家应该大致了解了node.js里面的流。之前说过在node里流还是很重要的,http里的request和response都是流。在下一篇文章我会写一个readStream和writeStream的简单实现。本人水平有限,有错误的地方希望指出。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐