一、需求分析
我们先来说说需求:将a.txt中的文本复制到b.txt中去。
二、普通fs完成需求
如果按照一般写法来说就是利用fs.read,fs.write直接读取内容然后写入内容,但这样会导致一个问题,a.txt文件大小有多大,我们在读写的时候就需要消耗多大的资源,但是我们不希望它占用太多资源
优化思路:指定缓冲区大小,只在缓冲区大小范围内进行多次读写,我们可以利用递归思路这样封装
const fs = require('fs')
function copy(source, target, cb) {
const BUFFER_SIZE = 3
const buffer = Buffer.alloc(BUFFER_SIZE)
const r_offset = 0
const w_offset = 0
//读入一部分数据就写入一部分数据
fs.open(source, 'r', function (err, rfd) {
fs.open(target, 'w', function (err, wfd) {
function next() {
fs.read(rfd, buffer, 0, BUFFER_SIZE, r_offset, function (err, bytesRead) {
if (err) return cb(err)
if (bytesRead) {
fs.write(wfd, buffer, 0, bytesRead, w_offset, function (err, written) {
r_offset = bytesRead
w_offset = written
nextTick()
})
} else {
fs.close(rfd, () => { })
fs.close(wfd, () => { })
cb()
}
})
}
next()
})
})
}
copy('./a.text', './b.text', function (err) {
if (err) return console.log(err)
console.log('copy success')
})
三、fs可读流
从代码风格上来看,此时fs的读写全耦合在一块,有没有更为简洁的书写方式呢,我们可以通过可读流来解决,可读流不是一下子将文件都读取完毕,而是可以控制读取的个数和读取的速率,同时代码风格也相当简洁,下面是基础用法
//可读流 不是一下子将文件都读取完毕,而是可以控制读取的个数和读取的速率
const fs = require('fs')
let rs = fs.createReadStream('./a.text', {
flags: 'r',
encoding: null,//编码就是buffer
autoClose: true,//相当于需要调用close方法
start: 0,
highWaterMark: 3 //每次读取数据的个数,默认是64*1024字节
})
//监听用户,绑定了data事件,会触发对应的回调,不停的触发
rs.on('open', function (fd) {
console.log(fd)
})
rs.on('data', function (chunk) {
console.log(fd)
rs.pause() //不再触发data事件
})
rs.on('end', function () {
console.log('end')
})
rs.on('close', function () {
console.log(close)
})
setInterval(() => {
rs.resume()//再次触发data事件
}, 1000)
四、手写可读流
1、初始化可读流属性
const EventEmitter = require('events')
const fs = require('fs')
class ReadStream extends EventEmitter {
constructor(path, options = {}) {
super()
this.path = path
this.flags = options.flags || 'r'
this.encoding = options.encoding || null
this.autoClose = options.autoClose || true
this.start = options.start || 0
this.end = options.end
this.highWaterMark = options.highWaterMark || 64 * 1024
}
}
module.exports = ReadStream
2、open()与read()
class ReadStream extends EventEmitter {
constructor(path, options = {}) {
//........ 省略部分代码
this.end = options.end
this.highWaterMark = options.highWaterMark || 64 * 1024
this.open() //文件打开操作,这个方法是异步的
this.read()
}
open() {
fs.open(this.path, this.flags, (err, fd) => {
this.fd = fd
this.emit('open', fd)
})
}
read(){
console.log(fd)
}
}
module.exports = ReadStream
此时会出现一个问题,read()该如何从open方法中拿到fd,open方法本身为异步方法,read()无法保证在open()后获取fd,并且必须在再监听到data事件后才开始read()方法
2.1处理open异步,data事件后read问题,open()方法报错
this.on('newListener',(type)=>{})可以监听每次新绑定的事件,当type === 'data'再去执行read()
3、通过发布订阅的方式处理open异步问题
const EventEmitter = require('events')
const fs = require('fs')
class ReadStream extends EventEmitter {
constructor(path, options = {}) {
//........ 省略部分代码
this.open() //文件打开操作,这个方法是异步的
//注意用户监听了data事件,才需要读取
this.on('newListener', function (type) {
if (type === 'data') {
this.read()
}
})
}
read() {//once events 模块中的绑定一次
//希望在open之后才能拿到fd
if (typeof this.fd !== 'number') {
return this.once('open', () => { this.read() })
}
console.log(this.fd)
}
destory(err) {
if (err) {
this.emit('error', err)
}
}
open() {
fs.open(this.path, this.flags, (err, fd) => {
if (err) {
return this.destory(err)
}
this.fd = fd
this.emit('open', fd)
})
}
module.exports = ReadStream
4、完善read()方法
read方法流程:
fd为空,则去调用open方法,fd不为空,说明open方法已经调用
分配读空间的缓存区,每次读取hightWaterMark大小的数据,递归调用read方法,读取完所有数据后,调用end
read() {//once events 模块中的绑定一次
//希望在open之后才能拿到fd
if (typeof this.fd !== 'number') {
return this.once('open', () => { this.read() })
}
const buffer = Buffer.alloc(this.highWaterMark)
fs.read(this.fd, buffer, 0, this.highWaterMark, this.offset, (err, bytesRead) => {
if (bytesRead) {
this.offset += bytesRead
this.emit('data', buffer.slice(0, bytesRead))
this.read()
} else {
this.emit('end')
}
})
}
4.1特殊情况处理:
当用户设定了读取的start和end时,每次读取大小不一定为hightWaterMark,判断边界条件:Math.min(this.end - this.offset + 1, this.highWaterMark)
tip: +1是因为end包后
read() {//once events 模块中的绑定一次
//希望在open之后才能拿到fd
if (typeof this.fd !== 'number') {
return this.once('open', () => { this.read() })
}
let howMutchToRead = this.end ? Math.min(this.end - this.offset + 1, this.highWaterMark) : this.highWaterMark
const buffer = Buffer.alloc(howMutchToRead)
fs.read(this.fd, buffer, 0, howMutchToRead, this.offset, (err, bytesRead) => {
if (bytesRead) {
this.offset += bytesRead
this.emit('data', buffer.slice(0, bytesRead))
this.read()
} else {
this.emit('end')
this.destory()
}
})
}
destory(err) {
if (err) {
this.emit('error', err)
}
if (this.autoClose) {
fs.close(this.fd, () => { this.emit('close') })
}
}
5、pause(),resume()
class ReadStream extends EventEmitter {
constructor(path, options = {}) {
//......省略部分代码
this.flowing = false // pause resume
//......省略部分代码
}
resume() {
if (!this.flowing) {
this.flowing = true
this.read()
}
}
pause() {
this.flowing = false
}
read() {//once events 模块中的绑定一次
fs.read(this.fd, buffer, 0, howMutchToRead, this.offset, (err, bytesRead) => {
if (bytesRead) {
//......省略部分代码
if (this.flowing) {
this.read()
}
}
})
}
//......省略部分代码
}
五、完整代码
const EventEmitter = require('events')
const fs = require('fs')
class ReadStream extends EventEmitter {
constructor(path, options = {}) {
super()
this.path = path
this.flags = options.flags || 'r'
this.encoding = options.encoding || null
this.autoClose = options.autoClose || true
this.start = options.start || 0
this.end = options.end
this.highWaterMark = options.highWaterMark || 64 * 1024
this.flowing = false // pause resume
this.open() //文件打开操作,这个方法是异步的
//注意用户监听了data事件,才需要读取
this.on('newListener', function (type) {
if (type === 'data') {
this.flowing = true
this.read()
}
})
this.offset = this.start
}
resume() {
if (!this.flowing) {
this.flowing = true
this.read()
}
}
pause() {
this.flowing = false
}
read() {//once events 模块中的绑定一次
//希望在open之后才能拿到fd
if (typeof this.fd !== 'number') {
return this.once('open', () => { this.read() })
}
let howMutchToRead = this.end ? Math.min(this.end - this.offset + 1, this.highWaterMark) : this.highWaterMark
const buffer = Buffer.alloc(howMutchToRead)
fs.read(this.fd, buffer, 0, howMutchToRead, this.offset, (err, bytesRead) => {
if (bytesRead) {
this.offset += bytesRead
this.emit('data', buffer.slice(0, bytesRead))
if (this.flowing) {
this.read()
}
} else {
this.emit('end')
this.destory()
}
})
}
destory(err) {
if (err) {
this.emit('error', err)
}
if (this.autoClose) {
fs.close(this.fd, () => { this.emit('close') })
}
}
open() {
fs.open(this.path, this.flags, (err, fd) => {
if (err) {
return this.destory(err)
}
this.fd = fd
this.emit('open', fd)
})
}
}
module.exports = ReadStream