Node.js fs模块(二)读写文件

Node.js fs模块(一)_qfc_128220的博客-CSDN博客

在前面一节中,我们介绍了fs.open,fs.read,fs.write,fs.close等方法的使用,但是我们发现实操起来比较繁琐,主要难点在于 读写文件前,需要先打开文件获取fd,并且需要设置好文件操作符,以及文件权限位,否则很容易导致读写过程发生错误,并且读写完文件后,也要人为关闭文件描述符fd,所以整体上很公式化。另外还有需要人为准备缓冲区,并控制缓冲区大小。

所以 Node.js 也提供了整合型方法,来实现一个方法实现文件操作,而内敛了fs.open,fs.close等繁琐操作。

目录

fs.readFile & fs.readFileSync

fs.readFile(path, options, callback)

fs.readFileSync(path, options)

模拟实现fs.readFile

关于fs.readFile性能的思考

fs.writeFile & fs.writeFileSync

fs.writeFile(file, data, options, callback)

fs.writeFileSync(file, data, options)

模拟实现fs.writeFile

fs.appendFile

fs.copyFile

fs.copyFile(src, dest, callback)

模拟实现fs.copyFile 


fs.readFile & fs.readFileSync

fs提供了一步到位的读取文件数据方法,有异步读取fs.readFile和同步读取fs.readFileSync

fs.readFile(path, options, callback)

形参解析

path要读取的文件所在路径,也可以是文件描述符fd
options
encoding文件内容的编码,默认null
flag文件操作符,默认"r"
callback
err读取文件过程异常信息
data读取文件成功后的数据,和options.encoding设置有关

可以发现读取操作简单了很多,我们再来测试下options.encoding对callback(data)的影响

 可以发现如果不指定options.encoding,则表示options.encoding为null,则data打印了buffer对象

如果指定了encoding为utf8,则data打印字符串,说明encoding作用于data数据格式。如果我们不在意data的数据格式的话,一般会省略options参数的书写,因为options.flag默认是r,和读取操作匹配。此时就更加精简了。

另外需要注意的是fs.readFile的path参数如果是路径的话,则fs.readFile底层自动执行fs.open,当读取完成后,自动执行fs.close,但是如果fs.readFile的path参数传入的是fd的话,则其底层不会执行fs.open和fs.close,即需要我们人为关闭fd,所以建议直接使用路径,而不是fd。

fs.readFileSync(path, options)

模拟实现fs.readFile

const fs = require('fs') // fs虽然是Node.js的内置模块,但是需要require引入,原因是节省内存
const path = require('path')
const { promisify } = require('util')

const open = promisify(fs.open)
const read = promisify(fs.read)

async function readFile(path, options, callback) {
  let fd
  try {
    fd = await open(path, options.flag, 0o666) // 自动打开fd

    const size = 2 // 每次读取的数据字节数
    let start = 0 // 从文件的哪个位置开始读取
    const arr = [] // 缓存每次读取出来的buffer

    while (true) {
      const { bytesRead, buffer } = await read(fd, Buffer.alloc(size), 0, size, start)
      if (bytesRead === 0) {
        break
      }
      start += size
      arr.push(buffer)
    }

    let buf = Buffer.concat(arr)
    if (options.encoding) {
      buf = buf.toString(options.encoding)
    }

    callback(null, buf)

  } catch (err) {
    callback(err, null)
  } finally {
    fs.close(fd) // 自动关闭fd
  }
}

readFile(path.resolve('test.txt'), { encoding: 'utf8', flag: 'r' }, (err, buffer) => {
  console.log(buffer);
})

console.log(111);

程序编写难度不是很大,主要难度在于一些工具方法的熟悉程度,如Buffer类常用方法的使用,以及fs.open,fs.read,fs.close,以及async await的用法,以及fs.readFile的形参含义和内部逻辑理解。以上只是完成了fs.readFile主干逻辑的模拟,其他一些校验逻辑没有写。

关于fs.readFile性能的思考

在模拟fs.readFile实现的过程中,我们发现,其中有一个size设计,它用来控制了缓冲区大小,以及每次从文件中读取的字节个数,如果每次读取结束时返回的bytesRead不为0,表示文件数据还没有读取完,则要继续下一次读取,最终导致了多次的fs.read调用。

而实际的fs.readFile逻辑中,也有类似size的设计

默认读取操作对应的缓冲区大小为64KB,也有512KB的。

这种固化缓冲区大小的操作,有两方面的考量:

1、被读取的文件数据有多少我们不知道,无法做到一次性准确读取完文件的所有数据

2、防止大文件读取时,创建超大缓冲区,造成服务器内存紧张,进而导致内存溢出

也就是说对于fs.readFile来说,会不断读取文件中数据,并传入缓冲区,当缓冲区满了,则取出缓冲数据,继续下一次读取数据,只有当文件数据读取完了(bytesRead为0),才停止读取动作。

所以fs.readFile会触发多次I/O操作,并且会长期占用内存去缓存每次读取完的数据,所以fs.readFile并不是一个性能友好,也不是一个内存友好的方法。

fs.writeFile & fs.writeFileSync

fs提供了一步到位的写入数据到文件的方法,包括异步写入fs.writeFile和同步写入fs.writeFileSync

fs.writeFile(file, data, options, callback)

形参解析

file参数,该参数可以传入一个路径(不要求一定存在),也可以传入文件描述符fd

data参数,要写入的数据,可以是字符串,也可以是Buffer对象

options.encoding参数,指定写入的data数据的编码,只有data是字符串才有效,需要注意data字符串的实际编码必须要指定的options.encoding相同,否则会发生乱码,默认为utf-8

options.mode参数,文件权限位,默认0o666,可读可写

options.flag参数,文件操作符,fs.writeFile的该参数默认为w,即覆盖写入

callback参数,错误优先的回调函数,err形参指向写入过程的错误信息

fs.writeFileSync(file, data, options)

 fs.writeFileSync是同步写入,各形参含义和fs.write相同,无特殊需求可以省略options参数,提高代码简洁度。

模拟实现fs.writeFile

const fs = require('fs') // fs虽然是Node.js的内置模块,但是需要require引入,原因是节省内存
const path = require('path')
const { promisify } = require('util')

const open = promisify(fs.open)
const write = promisify(fs.write)

async function writeFile(file, data, options, callback) {
  let fd
  try {
    fd = await open(file, options.flag, options.mode)

    const buf = Buffer.isBuffer(data) ? data : Buffer.from(data, options.encoding || 'utf8')

    await write(fd, buf, 0, buf.length, 0)

    callback(null)
  } catch (error) {
    callback(error)
  } finally {
    fs.close(fd)
  }
}

writeFile(path.resolve('test.txt'), 'abcdefg', { encoding: 'utf8', flag: 'w', mode: 0o666 }, err => {
  console.log(err);
})

由于写入的数据都在缓冲区中了,所以不需要顾及缓冲区的设计,只管从缓冲区buffer对象中拿数据就行了,所以性能上没有需要额外考虑的。

fs.appendFile

其实逻辑和fs.writeFile设计一致,fs.writeFile只需要改动options.flag参数值为'a',将相当于fs.appendFile了。

也就是说fs.appendFile的options.flag默认值是'a' , 而fs.writeFile的options.flag默认值是'w'

这就是二者的差别

如果将fs.writeFile的options.flag改为'a',则效果和fs.appendFile一致

另外 , 也有fs.appendFileSync同步追加数据到文件的方法 

fs.copyFile

fs.copyFile(src, dest, callback)

文件复制是非常常见的一种文件操作,fs模块也提供了专门的文件复制方法fs.copyFile

形参解析

src:目标文件路径

dest:拷贝文件路径

callback:回调函数,无参

操作简单明了,将src的文件,拷贝一份到dest的文件中,dest文件可以不存在。

模拟实现fs.copyFile 

const fs = require('fs') // fs虽然是Node.js的内置模块,但是需要require引入,原因是节省内存
const path = require('path')
const { promisify } = require('util')

const open = promisify(fs.open)
const read = promisify(fs.read)
const write = promisify(fs.write)

async function copyFile(src, dest, callback) {
  let rfd
  let wfd
  try {
    rfd = await open(src, 'r', 0o666)

    const size = 2
    let start = 0
    const arr = []

    while (true) {
      const { bytesRead, buffer } = await read(rfd, Buffer.alloc(size), 0, size, start)
      if (bytesRead === 0) {
        break
      }
      start += size
      arr.push(buffer)
    }

    let buf = Buffer.concat(arr)

    wfd = await open(dest, 'w', 0o666)

    await write(wfd, buf, 0, buf.length, 0)

    callback()
  } catch (err) {
    callback(err)
  } finally {
    fs.close(rfd)
    fs.close(wfd)
  }
}

copyFile(path.resolve('test.txt'), path.resolve('test_cpp.txt'), () => {
  console.log('拷贝完成');
})

fs.copyFile其实就是fs.readFile和fs.writeFile的结合,即先读取src数据,再将数据写入dest

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伏城之外

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值