从events模块到流(可读流,可写流)

events模块

先介绍一下基本的API

// 将events模块加载进来
let eventEmitter =  require('events');
// 这是一个工具箱模块,有兴趣的自行学习一下,这边只用到一个继承
let util = require('util');

function A() {}; // 创建一个类A
util.inherits(A, eventEmitter) // 让A继承eventEmitter公有方法

let a = new A;
a.on('eventName', (e) => {}); // 订阅一个事件,名字就叫eventName
a.on('eventName', (e) => {}); // 再绑定一个
a.emit('eventName', e) 
// 触发eventName订阅的事件,
// 并将参数e作为参数传入.如果事件有监听器就返回一个true,否则返回false
a.once('eventName', () => {})
// once和on比较,就是只能绑定一个监听器,之后的绑定都是无效的
a.setMaxListeners(3) // 设置可以绑定监听器的数量 最大监听器限制值 默认是10个
a.prependListener('eventName', () => {}) 
// 绑定一个监听器在所有监听器的最前面执行,就是一个执行顺序的问题
a.prependOnceListener('eventName', () => {})
//和once一样,只绑定一次
a.removeListener('eventName', callback); // 移除一个指定的监听器
a.removeAllListeners('eventName') // 移除所有监听器
复制代码

上面是events模块主要的API,所有的流都是eventEmitter的实例

let eventEmitter = require('events');
let util = require('util');

function A() {}

util.inherits(A, eventEmitter);

let a = new A;
a.on('test', (e) => {
    console.log(e, 'test1');
})

a.on('test', (e) => {
    console.log(e, 'test2');
})

a.prependOnceListener('test', (e) => {
    console.log(e, 'test3')
})

console.log(a.emit('test', 1, 2, 3))
// 执行结果顺序是
// 1 'test3'
// 1 'test1'
// 1 'test2'
// true
复制代码

可读流与可写流

可读流

let fs = require('fs');
let rs = fs.createReadStream(path, {
    flags, // 默认 'r'
    encoding, // 默认是null null代表的是buffer
    autoClose, // 默认true, 读取完自动关闭
    highWaterMark, // 默认是64k  64*1024b
    start, // 开始读取位置包括
    end, // 结束读取位置包括
})
rs.on('open', () => {}) // 文件打开时触发
rs.on('close', () => {}) // 文件关闭时触发
rs.on('error', () => {}) // 报错时触发
rs.on('data',function(data){  
//每读highWaterMark量的数据,就会触发一次,也就是说可能触发多次
    console.log(data);
    rs.pause(); // 暂停方法 表示暂停读取,暂停data事件触发
    rs.resume() // 继续读取
});
rs.on('end', () => {}); // 流结束时触发

文件 q.txt
123456

let fs = require('fs');
let path = require('path')

let rs = fs.createReadStream(path.join(__dirname, 'q.txt'), {
    highWaterMark: 2,
    // encoding: 'utf8'
})

rs.on('data', (data) => {
    console.log(data)
})
// 触发了三次输出结果就是
// <Buffer 31 32>
// <Buffer 33 34>
// <Buffer 35 36>
// 如果设置 encoding: 'utf8' 结果就是
// 12
// 34
// 56
复制代码

可写流

let fs = require('fs');
let ws = fs.createWriteStream(path, {
    flags, // 'w',
    autoClose, // true,
    highWaterMark, // 默认写是16k
    encoding, // 默认 null 就是buffer意思
})
let flag = ws.write(data, 'utf8', () => {})
// flag是布尔值表示是否可以继续写入
ws.on('drain', () => {})
// 缓存区满了,再被清空后出发drain



let ws = fs.createWriteStream('q.txt', {
    highWaterMark: 3
})

let i = 6
function write() {
    let flag = true;
    while(i && flag) {
        console.log('i', i);
        flag = ws.write(i-- + '', () => { console.log('ok') })
    }
}
write()
ws.on('drain', () => {
    console.log('drain')
    write()
})

// 输出结果, 因为是异步,可以不看'ok'
// 因为 'highWaterMark' 设置的是 3  
// 所以,ws.write运行到第三次返回的就是 false
i 6
i 5
i 4
ok
drain
i 3
i 2
i 1
ok
ok
ok
drain
ok
ok

复制代码

简单实现可读流源码


let EventEmitter = require('events');
let fs = require('fs');

class ReadStream extends EventEmitter {
    constructor(path, options) {
        super();
        this.path = path;
        this.flags = options.flags || 'r';
        this.autoClose = options.autoClose || true;
        this.highWaterMark = options.highWaterMark || 64*1024;
        this.start = options.start || 0;
        this.end = options.end;
        this.encoding = options.encoding || null;

        this.open(); // 打开文件 fd

        this.flowing = null; // null就是暂停模式
        // 看是否坚挺了打他时间,如果监听了 就变成流动模式

        // 建立一个buffer 这个buffer就是要一次读多少
        this.buffer = Buffer.alloc(this.highWaterMark);

        this.pos = this.start; // 开始读取的位置

        this.on('newListener', (eventName, callback) => {
            if (eventName === 'data') {
                // 相当于用户监听了data事件
                this.flowing = true;
                // 监听了 就去读
                this.read();
            }
        })
    }
    read() {
        // 此时文件还没打开呢
        if(typeof this.fd !== 'number') {
            return this.once('open', () => {this.read()})
        }
        let howMuchToRead = this.end ? Math.min(this.highWaterMark, this.end - this.pos + 1) : this.highWaterMark;
        fs.read(this.fd, this.buffer, 0, howMuchToRead, this.pos, (err, bytesRead) => {
            if (bytesRead > 0) {
                this.pos += bytesRead;
                let data = this.encoding ? this.buffer.slice(0, bytesRead).toString(this.encoding) : this.buffer.slice(0, bytesRead);
                this.emit('data', data);
                // 当读取的位置 大于了末尾 就是读取完毕了
                if (this.pos > this.end) {
                    this.emit('end');
                    this.destroy();
                }
                if (this.flowing) {
                    this.read();
                }
            } else {
                this.emit('end');
                this.destroy();
            }
        })
    }
    pipe(ws) {
        this.on('data', (chunk) => {
            let flag = ws.write(chunk);
            if (!flag) {
                this.pause();
            }
        })
        ws.on('drain', () => {
            this.resume();
        })
    }
    resume() {
        this.flowing = true;
        this.read();
    }
    pause() {
        this.flowing = false;
    }
    destroy() {
        // 先判断有没有fd 有关闭文件 触发close事件
        if(typeof this.fd === 'number') {
            fs.close(this.fd, () => {
                this.emit('close');
            })
            return
        }
        this.emit('close')
    }
    open() {
        // 先打开文件
        fs.open(this.path, this.flags, (err, fd) => {
            if(err) {
                this.emit('error', err);
                if(this.autoClose) {
                    this.destroy();
                }
                return;
            }
            this.fd = fd; // 记录文件描述符
            this.emit('open'); // 文件打开了
        })
    }
}
module.exports = ReadStream;
复制代码

简单实现可写流源码

let EventEmitter = require('events');
let fs = require('fs');
class WriteStream extends EventEmitter{
    constructor(path, options) {
        super();
        this.path = path;
        this.highWaterMark = this.highWaterMark || 16*1024;
        this.autoClose = this.autoClose || true;
        this.mode = options.mode;
        this.start = options.start || 0;
        this.flags = options.flags || 'w';
        this.encoding = options.encoding || 'utf8';

        // 可写流 要有一个缓存区,当正在写入文件时,内容需要写入到缓存中
        // 在源码中是一个链表 => []

        this.buffers = [];

        // 标识 是否正在写入
        this.writing = false;

        // 是否满足触发drain事件
        this.needDrain = false;

        // 记录写入的位置
        this.pos = 0;

        // 记录缓存区的大小
        this.length = 0;
        this.open();
    }
    destroy() {
        if(typeof this.fd !== 'number') {
            return this.emit('close');
        }
        fs.close(this.fd, () => {
            this.emit('close')
        })
    }
    open() {
        fs.open(this.path, this.flags, this.made, (err, fd) => {
            if(err) {
                this.emit('error', err);
                if(this.autoClose) {
                    this.destroy();
                }
                return
            }
            this.fd = fd;
            this.emit('open');
        })
    }
    write(chunk, encoding = this.encoding, callback = () => {}){
        chunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding);
        // write 返回一个boolean类型
        this.length += chunk.length;
        let ret = this.length < this.highWaterMark; // 比较是否达到了缓存区的大小
        this.needDrain = !ret; // 是否需要触发drain
        // 判断是否正在写入 如果是正在写入 就写到缓存区中
        if(this.writing) {
            this.buffers.push({
                encoding,
                chunk,
                callback
            });
        } else {
            // 专门用来将内容 写入到文件内
            this.writing = true;
            this._write(chunk, encoding, () => {
                callback();
                this.clearBuffer();
            })
        }
        return ret;
    }
    clearBuffer() {
        let buffer = this.buffers.shift();
        if(buffer){
            this._write(buffer.chunk, buffer.encoding, () => {
                buffer.callback();
                this.clearBuffer();
            })
        } else {
            this.writing = false;
            if (this.needDrain) {
                this.needDrain = false;
                this.emit('drain');
            }
        }
    }
    _write(chunk, encoding, callback) {
        if(typeof this.fd !== 'number') {
            return this.once('open', () => this._write(chunk, encoding, callback));
        }
        fs.write(this.fd, chunk, 0, chunk.length, this.pos, (err, byteWritten) => {
            this.length -= byteWritten;
            this.pos += byteWritten;

            callback(); // 清空缓存区的内容
        })
    }
}
module.exports = WriteStream;
复制代码

有错误的地方还请指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值