流的基本概念及理解
流是一种数据传输手段,是有顺序的,有起点和终点,比如你要把数据从一个地方传到另外一个地方
流非常重要,gulp,webpack,HTTP里的请求和响应,http里的socket都是流,包括后面压缩,加密等
流为什么这么好用还这么重要呢?
- 因为有时候我们不关心文件的主体内容,只关心能不能取到数据,取到数据之后怎么进行处理
- 对于小型的文本文件,我们可以把文件内容全部读入内存,然后再写入文件,比如grunt-file-copy
- 对于体积较大的二进制文件,比如音频、视频文件,动辄几个GB大小,如果使用这种方法,很容易使内存“爆仓”。
- 理想的方法应该是读一部分,写一部分,不管文件有多大,只要时间允许,总会处理完成,这里就需要用到流的概念
流是一个抽象接口,被Node中很多对象所实现,比如HTTP服务器request和response对象都是流
Node.js 中有四种基本的流类型:
- Readable - 可读的流 (例如 fs.createReadStream()).
- Writable - 可写的流 (例如 fs.createWriteStream()).
- Duplex - 可读写的流 (例如 net.Socket).
- Transform - 在读写过程中可以修改和变换数据的 Duplex 流 (例如 zlib.createDeflate()).
可以通过 require(‘stream’) 加载 Stream 基类。其中包括了 Readable 流、Writable 流、Duplex 流和 Transform 流的基类
Readable streams可读流
可读流(Readable streams)是对提供数据的 源头(source)的抽象
可读流的例子包括:
- HTTP responses, on the client :客户端请求
- HTTP requests, on the server :服务端请求
- fs read streams :读文件
- zlib streams :压缩
- crypto streams :加密
- TCP sockets :TCP协议
- child process stdout and stderr :子进程标准输出和错误输出
- process.stdin :标准输入
所有的 Readable 都实现了 stream.Readable 类定义的接口
通过流读取数据
- 用Readable创建对象readable后,便得到了一个可读流
- 如果实现_read方法,就将流连接到一个底层数据源
- 流通过调用_read向底层请求数据,底层再调用流的push方法将需要的数据传递过来
- 当readable连接了数据源后,下游便可以调用readable.read(n)向流请求数据,同时监听readable的data事件来接收取到的数据
下面简单举个可读流的例子:
- 监听可读流的data事件,当你一旦开始监听data事件的时候,流就可以读文件的内容并且发射data,读一点发射一点读一点发射一点
- 默认情况下,当你监听data事件之后,会不停的读数据,然后触发data事件,触发完data事件后再次读数据
- 读的时候不是把文件整体内容读出来再发射出来的,而且设置一个缓冲区,大小默认是64K,比如文件是128K,先读64K发射出来,再读64K在发射出来,会发射两次
- 缓冲区的大小可以通过highWaterMark来设置
let fs = require('fs');
//通过创建一个可读流
let rs = fs.createReadStream('./1.txt',{
flags:'r',//我们要对文件进行何种操作
mode:0o666,//权限位
encoding:'utf8',//不传默认为buffer,显示为字符串
start:3,//从索引为3的位置开始读
//这是我的见过唯一一个包括结束索引的
end:8,//读到索引为8结束
highWaterMark:3//缓冲区大小
});
rs.on('open',function () {
console.log('文件打开');
});
rs.setEncoding('utf8');//显示为字符串
//希望流有一个暂停和恢复触发的机制
rs.on('data',function (data) {
console.log(data);
rs.pause();//暂停读取和发射data事件
setTimeout(function(){
rs.resume();//恢复读取并触发data事件
},2000);
});
//如果读取文件出错了,会触发error事件
rs.on('error',function () {
console.log("error");
});
//如果文件的内容读完了,会触发end事件
rs.on('end',function () {
console.log('读完了');
});
rs.on('close',function () {
console.log('文件关闭');
});
/**
文件打开
334
455
读完了
文件关闭
**/
可读流的简单实现
let fs = require('fs');
let ReadStream = require('./ReadStream');
let rs = ReadStream('./1.txt', {
flags: 'r',
encoding: 'utf8',
start: 3,
end: 7,
highWaterMark: 3
});
rs.on('open', function () {
console.log("open");
});
rs.on('data', function (data) {
console.log(data);
});
rs.on('end', function () {
console.log("end");
});
rs.on('close', function () {
console.log("close");
});
/**
open
456
789
end
close
**/
let fs = require('fs');
let EventEmitter = require('events');
class ReadStream extends EventEmitter {
constructor(path, options) {
super(path, options);
this.path = path;
this.highWaterMark = options.highWaterMark || 64 * 1024;
this.buffer = Buffer.alloc(this.highWaterMark);
this.flags = options.flags || 'r';
this.encoding = options.encoding;
this.mode = options.mode || 0o666;
this.start = options.start || 0;
this.end = options.end;
this.pos = this.start;
this.autoClose = options.autoClose || true;
this.bytesRead = 0;
this.closed = false;
this.flowing;
this.needReadable = false;
this.length = 0;
this.buffers = [];
this.on('end', function () {
if (this.autoClose) {
this.destroy();
}
});
this.on('newListener', (type) => {
if (type == 'data') {
this.flowing = true;
this.read();
}
if (type == 'readable') {
this.read(0);
}
});
this.open();
}
open() {
fs.open(this.path, this.flags, this.mode, (err, fd) => {
if (err) {
if (this.autoClose) {
this.destroy();
return this.emit('error', err);
}
}
this.fd = fd;
this.emit('open');
});
}
read(n) {
if (typeof this.fd != 'number') {
return this.once('open', () => this.read());
}
n = parseInt(n,