Readable Streams的使用与简单的实现原理

1. 可读流使用方法

1.1 引入fs模块

const fs = require('fs');

1.2 创建可读流

fs.createReadStream(path,[options]);

1.3 可读流参数

  • path:文件路径
  • options

    • encoding
      设置读取编码,默认值:null,使用buffer方式读取
      可选参数:utf8|ascii|base64
    • flags
      对文件进行何种操作,默认值:r
      可选参数:

      符号 含义
      r 读文件,文件不存在报错
      rs 同步读取文件并忽略缓存
    • mode
      操作权限,默认值:0o666

    • fd
      文件描述符,默认值:null
    • autoClose
      自动关闭,默认值:true,对错误或结束的文件描述符将自动关闭
      可选参数:true|false
    • start
      从指定索引开始读取,默认值:0
      可选参数:number
    • end
      读取到指定索引结束(包含索引位)
      可选参数:number
    • highWaterMark
      最高水位线,读取缓存区默认大小:64KB
      可选参数:number
  • 示例
fs.createReadStream('./1.txt',{
    flags: 'r',         //对文件做何种操作
    mode: 0o666,        //操作权限
    encoding: 'utf-8',  //以指定编码读取文件,如果不设置默认以buffer方式读取
    start: 3,           //从指定索引开始读取
    end: 9,             //读取到指定索引结束
    highWaterMark:3,    //水位线,缓存区设置每次读取多少
    autoClose: true     //自动关闭
});

1.4 事件监听

  • open
    监听文件打开事件
rs.on('open',()=>{
    console.log('打开文件');
});
  • data
    监听data事件,开始监听data事件,流开始读取文件并发送到回调函数中
rs.on('data',(data)=>{
    console.log(data);
});
  • error
    监听error事件,文件读取失败触发error事件
rs.on('error',(err)=>{
    cosole.log('读取失败:'+err);
});
  • end
    监听end事件,文件读取完成触发end事件
rs.on('end',()=>{
    console.log('读取完成');
});
  • close
    如果设置autoClose为true,当文件执行失败或执行完毕会触发close事件,关闭文件
rs.on('close',()=>{
    console.log('关闭文件');
});

1.5 暂停和恢复data

rs.on('data',(data)=>{
    console.log(data);
    rs.pause();         //暂停读取和发送data事件
    setTimeout(() => {
        rs.resume();    //恢复读取和发送data事件
    }, 2000);
});

2. 可读流实现原理

2.1 创建自定义模块

const EventEmitter = require('events'); //引入事件模块
const fs = require('fs');               //引入文件系统模块

class ReadStream extends EventEmitter{  //创建ReadStream类,继承events事件
    constructor(path,options={}){
        super();
    }
}
module.exports = ReadStream;    //导出模块

2.2 自定义模块引入

  • 模块引入顺序
    模块分为:系统模块、自定义模块、第三方模块
    1. 系统模块优先级最高
    2. 自定义模块引入如果没有加’./’就从系统模块里找,系统模块里面找不到就再node_modules文件夹里找
    3. 第三方模块不需要’./’的形式引入,可以直接通过包名将文件引入,查找package.json中的main对应的文件运行,如果当前目录下没找到,会一直向上一级目录查找,直到找到根目录为止
  • 使用自定义ReadStream模块
const ReadStream = require('./ReadStream');
let rs = new ReadStream(path,[options]);

2.3 创建实例

  • 在ReadStream类中创建实例
class ReadStream extends EventEmitter{  //创建ReadStream类,继承events事件
    constructor(path,options={}){
        super();
        //创建实例
        this.path = path;   //文件路径
        this.autoClose = options.autoClose || true; //是否自动关闭,默认为true
        this.flags = options.flags || 'r';  //操作方式,默认为r
        this.encoding = options.encoding || null;   //设置编码,默认buffer
        this.start = options.statr || 0;    //开始读取位置,默认为0
        this.end = options.end || null;     //读取结束位置,默认为null,读取到最后一位
        this.pos = this.start;              //计算偏移量
        this.highWaterMark = options.highWaterMark || 64*1024;  //最高水位线,默认64KB
        this.flowing = null;    //控制当前是否流动模式
        this.buffer = Buffer.alloc(this.highWaterMark); //构建读取到的内容的buffer
        //创建可读流,调用打开文件方法
        this.open();    //异步执行
        //检查用户是否监听了data事件
        this.on('newListener',(type)=>{
            if(type === 'data'){    //用户监听了data事件,准备开始读取文件
                this.flowing = true;    //变为流动模式
                this.read();        //执行读取文件方法
            }
        })
    }
    //'后面的方法都写在这里'
}

2.4 打开文件操作

open(){
    fs.open(this.path, this.flags, (err,fd)=>{  //(文件路径,操作方式,CallBackFn(错误,文件描述符))
        if(err){
            this.emit('error',err); //如果文件出错调用错误方法
            if(this.autoClose){     //如果设置自动关闭,文件出错将文件关闭
                this.destroy();     //执行销毁方法(触发close事件)
            }
            return;                 //终止文件打开操作
        }
        this.fd = fd;               //如果没错误就讲获取的fd保存起来
        this.emit('open');          //触发文件打开事件
    });
}

2.5 销毁文件操作

destroy(){
    if(typeof this.fd === 'number'){    //判断文件是否打开过,如果打开后出错执行fs.close方法将文件关闭
        fs.close(this.fd,()=>{          //异步操作,文件还在监听,需要执行close事件
            this.emit('close');
        });
        return;
    }
    this.emit('colse');                 //如果没有fd证明文件没有被打开过,可以直接执行close事件
}

2.6 读取文件操作

read(){ //同步
    if(typeof this.fd !== 'number'){    //检查是否获取到fd,如果没获取到就证明文件还没被打开
        return this.once('open',()=>this.read());  //如果文件没打开就继续调用自己,等文件打开后再继续执行下一步
    }

    //计算读取范围,如果没有this.end就读取到最后一位,如果有就计算偏移量
    let howMuchToRead = this.end ? Math.min(this.highWaterMark, this.end - this.pos + 1) : this.highWaterMark;

    //获取到fd后开始读取  read(文件描述符,读取到哪个buffer,读取到buffer的哪个位置,往buffer读取几个,读取的位置)
    fs.read(this.fd, this.buffer, 0, howMuchToRead, this.pos, (err,bytesRead)=>{
        if(bytesRead > 0){  //读取到内容
            this.pos += bytesRead;  //计算偏移量
            let r = this.buffer.slice(0,bytesRead); //保留有用的
            r = this.encoding ? r.toString(this.encoding) : r;  //判断是否设置编码,按照指定编码转换成字符串
            this.emit('data',r);    //第一次读取
            if(this.flowing) this.read();   //如果是流动的就一直读取
        }else{
            this.emit('end');   //读取完成
            this.destroy();     //销毁
        }
    });
}

2.7 暂停读取操作

pause(){
    this.flowing = false;   //关闭流动模式
}

2.8 恢复读取操作

resume(){
    this.flowing = true;    //开启流动模式
    this.read();            //继续读取
}

你可能感兴趣的:(nodejs,Stream)