系统性Node.js——手写文件流( 二 )

  • 这里当我们监听到data事件绑定(即 on('data'))时 , 就开始读取文件即 this.read() , this.read() 是我们核心方法 。
  • openopen 方法如下:
    open() {fs.open(this.path, this.flags, (err, fd) => {if (err) {// 文件打开失败触发 error 事件this.emit('error', err)return}// 记录文件标识符this.fd = fd// 文件打开成功后触发 open 事件this.emit('open')})}当打开文件后记录下文件标识符 , 即 this.fd
    readread 方法如下:
    read() {// 由于 ```fs.open``` 是异步操作,// 所以当调用 read 方法时 , 文件可能还没有打开// 所以我们要等 open 事件触发之后 , 再次调用 read 方法if (typeof this.fd !== 'number') {this.once('open', () => this.read())return}// 申请一个 highWaterMark 字节的 buffer ,// 用来存储从文件读取的内容const buf = Buffer.alloc(this.highWaterMark)// 开始读取文件// 每次读取时 , 都记录下文件的偏移量fs.read(this.fd, buf, 0, buf.length, this.offset, (err, bytesRead) => {this.offset += bytesRead// bytesRead 为实际读取的文件字节数// 如果 bytesRead 为 0 , 则代表没有读取到内容 , 即读取完毕if (bytesRead) {// 每次读取都触发 data 事件this.emit('data', buf.slice(0, bytesRead))// 如果处于流动状态 , 则继续读取// 这里当调用 pause 方法时 , 会将 this.flowing 置为 falsethis.flowing--tt-darkmode-color: #595959;">上述每行代码都有注释 , 相信也不难理解 , 这里有几个关键点要注意一下
    • 一定要等文件打开后才能开始读取文件 , 但是文件打开是一个异步操作 , 我们并不知道具体的打开完毕时间 , 所以 , 我们会在文件打开后触发一个 on('open') 事件 , read 方法内会等 open 事件触发后再次重新调用 read()
    • fs.read() 方法之前有讲过 , 可以从前文回顾里看一下 手写 fs 核心方法
    • this.flowing 属性是用来判断是否是流动的 , 会用对应的 pasue() 方法与 resume() 来控制 , 下面我们来看一下这两个方法 。
    pausepause() {this.flowing =false}resumeresume() {if (!this.flowing) {this.flowing = truethis.read()}}完整代码const { EventEmitter } = require('events')const fs = require('fs')class ReadStream extends EventEmitter {constructor(path, options = {}) {super()this.path = paththis.flags = options.flags ?? 'r'this.encoding = options.encoding ?? 'utf8'this.autoClose = options.autoClose ?? truethis.start = options.start ?? 0this.end = options.end ?? undefinedthis.highWaterMark = options.highWaterMark ?? 16 * 1024this.offset = this.startthis.flowing = falsethis.open()this.on('newListener', (type) => {if (type === 'data') {this.flowing = truethis.read()}})}open() {fs.open(this.path, this.flags, (err, fd) => {if (err) {this.emit('error', err)return}this.fd = fdthis.emit('open')})}pause() {this.flowing =false}resume() {if (!this.flowing) {this.flowing = truethis.read()}}read() {if (typeof this.fd !== 'number') {this.once('open', () => this.read())return}const buf = Buffer.alloc(this.highWaterMark)// const howMuchToRead = Math.min(this.end - this.start + 1, buf.length)fs.read(this.fd, buf, 0, buf.length, this.offset, (err, bytesRead) => {this.offset += bytesReadif (bytesRead) {this.emit('data', buf.slice(0, bytesRead))this.flowing--tt-darkmode-color: #595959;">文件可读流总结可以看到 , 我们用了不到 70 行代码就实现了一个可读流 , 所以原理其实并没有想象中那么难 , 相信大家也很容易就可以掌握 。


    推荐阅读