初识 Node.js stream

295 阅读3分钟

什么是 stream

stream(流)是一种处理流数据的抽象接口,Node.js 提供了 stream 模块用来支持这种数据结构。通过 stream 的 API 可以构建实现流接口的对象。

Node.js 很多地方都用到了流,比如 http 的 request 对象和 fs 的读取流等。

stream 有什么用

就拿 fs.readFile 来说,readFile 方法会一次性把所有数据读取到内存中,如果文件体积过大,那么会占用过多内存。

stream 的特点是将数据放到缓冲器(Buffer)中,有序的读取部分数据到内存中,这样就解决了内存占用的问题。

stream 的分类

  • 可读流(Readable)
  • 可写流(Writable)
  • 既可读又可写流(Duplex)

可读流(Readable Stream)

1、两种工作模式

  • flowing(流动)模式:可读流自动从系统底层读取数据,并通过 EventEmitter 接口的事件尽快将数据提供给应用。
  • paused(暂停)模式:必须显式调用 stream.read() 方法来从流中读取数据片段。

2、创建可读流

以 fs 模块为例,下面是一个简单的文件读取流:

let fs = require('fs');
// 假设当前目录下有一个 example.txt 文件,内容为:Hello Node.js!
let rs = fs.createReadStream('example.txt', 'utf8'); // 创建可读流

3、事件

3.1 data 事件

读取流默认情况下是 paused 模式,可以通过监听 data 事件将其转换为 flowing 模式,在 data 事件的回调函数中可以读取到流的信息。

let fs = require('fs');
let rs = fs.createReadStream('example.txt', 'utf8'); 

rs.on('data', chunk => {
    console.log(chunk);
});
// -> Hello Node.js!

3.2 end 事件

当数据全部读取完毕,end 事件会被触发

let fs = require('fs');
let rs = fs.createReadStream('example.txt', 'utf8');
rs.on('data', chunk => {
    console.log(chunk);
});
rs.on('end', () => {
    console.log('END');
});
// -> Hello Node.js!
// -> END

3.3 open、close、error 事件

open 事件在文件读取前被调用,close 事件在 end 事件之后被调用,error 事件顾名思义在读取出错时被调用。

let fs = require('fs');
let rs = fs.createReadStream('example.txt', 'utf8');

rs.on('data', chunk => {
    console.log(chunk);
});
rs.on('end', () => {
    console.log('END');
});
rs.on('open', () => {
    console.log('OPEN');
});
rs.on('close', () => {
    console.log('CLOSE');
});
rs.on('error', (err) => {
    console.log(err);
});
// -> OPEN!
// -> Hello Node.js!
// -> END
// -> CLOSE

4、方法

4.1 pause() 方法

flowing 模式会不断的读取数据,直到数据全部读取完毕。

let fs = require('fs');
// Node.js 的缓冲器默认为 64k,为了演示 pause() 方法,假设有一个大小为 180k 的 jpg 图片
let rs = fs.createReadStream('image.jpg');
let count = 0;

rs.on('data', chunk => {
    console.log('读取' + (++count) + '次');
});
// -> 读取1次
// -> 读取2次
// -> 读取3次

pause() 方法可以使 flowing 模式的流停止触发 data 事件,从而切出 flowing 模式。

let fs = require('fs');
// 假设有一个大小为 180k 的 jpg 图片
let rs = fs.createReadStream('image.jpg');
let count = 0;

rs.on('data', chunk => {
    console.log('读取' + (++count) + '次');
    rs.pause();
});
// -> 读取1次

上面的代码只读取了一次,遇到 rs.pause() 就暂停了。

4.2 resume() 方法

与 pause() 相对应,恢复事件的发送。

let fs = require('fs');
// 假设有一个大小为 180k 的 jpg 图片
let rs = fs.createReadStream('image.jpg');
let count = 0;

rs.on('data', chunk => {
    console.log('读取' + (++count) + '次');
    rs.pause();
});
setInterval(function () {
    rs.resume();
}, 1000);
// -> 读取1次
// 1s 后
// -> 读取2次
// 1s 后
// -> 读取3次

可写流(Writable Stream)

1、创建可写流

let fs = require('fs');
let rs = fs.createWriteStream('example2.txt', 'utf8'); // 创建写入流
// 在当前目录下创建一个空文本文件 example2.txt

2、方法

2.1 write() 方法

写入数据,当数据必须要在内部被缓冲时,返回 false。

let fs = require('fs');
let rs = fs.createWriteStream('example2.txt', 'utf8'); // 创建写入流
// 在当前目录下创建一个空文本文件 example2.txt
rs.write('人家四月芳菲尽,山寺桃花始盛开。');
rs.write('长恨春归无觅处,不知转入此中来。');

要以流的形式写入文件,只需要不断调用 write() 方法。

2.2 end() 方法

调用 end(),写完之后就终止新的写入了。

let fs = require('fs');
let rs = fs.createWriteStream('example2.txt', 'utf8');

rs.write('人家四月芳菲尽,山寺桃花始盛开。');
rs.write('长恨春归无觅处,不知转入此中来。');
rs.end();

3、事件

  • drain 事件:表明数据还没有写完,调用 write() 方法返回 false。
  • finish 事件:在调用了 end() 方法,且缓冲区的数据都已经写完时被触发。