简介

流是数据的集合 —— 就像数组或字符串一样。区别在于流中的数据可能不会立刻就全部可用,并且你无需一次性地把这些数据全部放入内存。这使得流在操作大量数据或是数据从外部来源逐段发送过来的时候变得非常有用。

一个小例子

创建一个大文件

先写一个makeFile.js脚本来创建一个大文件

const fs = require('fs')
const file = fs.createWriteStream('./big.file')
for(let i = 0;i<1e6;i++){
    file.write('i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end i love front-end \n')
}
file.end()

图片说明
然后开启服务,创建文件server.js

const fs = require('fs');
const server = require('http').createServer();

server.on('request', (req, res) => {
  fs.readFile('./big.file', (err, data) => {
    if (err) throw err;

    res.end(data);
  });
});

server.listen(8000,()=>{
    console.log(process.pid) //5400
});

这边先看一下程序所占用的内存,是没有任何问题的
图片说明
然后我们开始访问服务,可以看到所占用的内存疯狂增长
图片说明
因为node一次性把内容读进了内存中。

使用流

我们再来使用node中的流来处理这个问题,稍稍改一下server.js

const fs = require('fs');
const server = require('http').createServer();

server.on('request', (req, res) => {
    let rs = fs.createReadStream('./big.file')
    rs.on('data', data => {
        res.end(data)
    })
});

server.listen(8000, () => {
    console.log(process.pid)
});

这个时候再去访问服务,可以看到内存虽然也是增长了,但是没有刚才那么夸张,只是增长了一点点而已.图片说明
由此我们可以看出流在处理文件时候的威力之大!

流的API

可读流

创建可读流

var rs = fs.createReadStream(path,[options])
path为读取文件的路径
options常用参数

  • flags打开文件要做的操作,默认为'r'
  • encoding默认为null
  • start开始读取的索引位置
  • end结束读取的索引位置(包括结束位置)
  • highWaterMark读取缓存区默认的大小64kb

监听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);
});

暂停和恢复触发data

通过pause()方法和resume()方法

rs.on('data', function (data) {
    rs.pause();
    console.log(data);
});
setTimeout(function () {
    rs.resume();
},2000);

可写流

创建可写流

var ws = fs.createWriteStream(path,[options]);
path写入的文件路径
options常用参数

  • flags打开文件要做的操作,默认为'w'
  • encoding默认为utf8
  • highWaterMark写入缓存区的默认大小16kb

write方法

ws.write(chunk,[encoding],[callback]);

  • chunk写入的数据buffer/string
  • encoding编码格式chunk为字符串时有用,可选
  • callback 写入成功后的回调

返回值为布尔值,系统缓存区满时为false,未满时为true

end方法

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

drain方法

当一个流不处在 drain 的状态, 对 write() 的调用会缓存数据,并且返回 false。一旦所有当前所有缓存的数据块都排空了,那么 'drain' 事件就会被触发

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('所有的写入已经完成!');
});

pipe方法

pipe方法的意思是管道,可以控制速率。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。

var fs = require("fs");
// 创建一个可读流
var readerStream = fs.createReadStream('input.txt');
// 创建一个可写流
var writerStream = fs.createWriteStream('output.txt');
// 管道读写操作
// 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中
readerStream.pipe(writerStream);
  • 会监听rs的on('data'),将读取到的内容调用ws.write方法
  • 调用写的方法会返回一个boolean类型
  • 如果返回了false就调用rs.pause()暂停读取
  • 等待可写流写入完毕后 on('drain')在恢复读取 pipe方法的使用