Node.js 内置模块之 fs

1,562 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情

fs(File system,文件系统) 是 node 的一个内置模块,可用于在多种操作系统中(Windows 、macOS 等)对文件进行操作。查阅 node 的官方文档,可以看到 File system 下有非常多的 API:
image.png

它们有的用于对文件进行读写操作,有的用于对文件夹操作等,且都提供同步异步Promise 这 3 种操作方式。下面举几个常用的 API 为例,进行说明。

操作文件

读取文件

同步读取

fs.readFileSync() 用于同步 (Sync) 读取文件,直接传入要读取的文件的路径(注意,如果使用相对路径,相对的是执行 node 命令所处的路径,而不是相对当前文件模块,所以最好使用绝对路径)或文件描述符即可:

// 代码块 1.1.1
const fs = require('fs')
const path = require('path')
// 同步读取
const resSync = fs.readFileSync(path.resolve(__dirname, './test.txt'))
console.log(resSync)
console.log('看看会不会阻塞')

执行结果如下,第 6 行的打印是在第 5 行之后,说明同步操作会阻塞后续代码的运行:

image.png

resSync 的结果是 Buffer 对象,本质上是二进制的内容,只是以十六进制来展示。如果想看到文本内容,可以对结果使用 toString() 进行转换:console.log(resSync.toString()),或者给 fs.readFileSync() 传入第 2 个参数 —— 一个用于指定配置的可选对象,该对象有 2 个属性:

  • encoding,默认值为 null ,也就是结果显示为 buffer,想显示文本可以改为 'utf-8' (或 'utf8');
  • flag,读取文件时默认值为 'r',更多 flag 相关信息可查看官方文档
// 代码块 1.1.2
const resSync = fs.readFileSync('./test.txt', {
  encoding: 'utf-8',
  flag: 'r'
})
console.log(resSync)
console.log('看看会不会阻塞')

现在看到的 resSync 就为一个字符串了:

image.png

异步读取 - Callback

fs.readFile() 用于异步读取文件,其可以传入 3 个参数,前两个与 fs.readFileSync() 相同,第 3 个参数为一个回调函数,当获取到读取结果时被调用:

// 代码块 1.2.1
fs.readFile(
  './test.txt',
  {
    encoding: 'utf-8'
  },
  (err, data) => {
    if (!err) console.log(data)
  }
)
console.log('看看会不会阻塞')

执行结果如下,可以看到异步读取不会阻塞后续代码的执行:

image.png

异步读取 - Promise

fs.promises.readFile() 也是用于异步读取文件,但可以避免在采用回调函数的方式获取结果时容易产生的回调地狱:

// 代码块 1.3.1
fs.promises
  .readFile('./test.txt', {
    encoding: 'utf-8'
  })
  .then(res => {
    console.log(res)
  })
  .catch(err => console.log(err))

写入文件

写入文件也有同步异步的方法,下面以异步 - callback 的 api fs.writeFile()为例。
其第 1 个参数可以是文件的地址或文件描述符,如果文件不存在,则会进行创建;
第 2 个参数为要写入的内容;
第 3 个参数为用于指定配置的可选对象,其中有之前介绍过的两个属性 encodingflag,只不过这里 encoding 的默认值为 'utf8' flag 的默认值为 'w',即默认写入的内容会覆盖原有的内容,如果是想在原内容后追加写入,则可以改为 'a' (append);
第 4 个参数是一个回调函数,在写入操作执行结束后调用,如果写入发生错误,就会把错误信息作为参数传入:

fs.writeFile(
  './test.txt',
  'Hello Juejin',
  {
    encoding: 'utf8',
    flag: 'w'
  },
  err => {
    if (err) {
      console.log(err)
      return
    }
    console.log('写入成功')
  }
)

文件描述符

前面说到,读取文件的这 3 种方法,传入的第一个参数除了文件的路径外,还可以是文件描述符(file descriptor)。在常见的操作系统中,内核对于每个进程都维护着一张当前打开的文件和资源的表格,而每个打开的文件都会被分配一个简单的数字用于标识和跟踪文件,虽然不同的操作系统具体实现可能不同,但 node 帮我们处理了差异,为每个打开的文件分配了个数字类型的文件描述符。

我们可以通过 fs.open() 打开一个文件来获取文件描述符:

// 代码块 2.1.1
fs.open('./test.txt', (err, fd) => {
  if (!err) {
    console.log(fd)
  }
})

查看类型可以得知其第 2 个参数,也就是回调函数会被传入两个参数,第 1 个为错误信息 err,第 2 个就是文件描述符 fd

image.png

得到的结果为 3:

image.png

查看文件信息

获取到了文件描述符,我们可以通过 fs.fstat() (其第 1 个参数只能是 fd)查看文件信息:

fs.open('./test.txt', (err, fd) => {
  if (err) return
  fs.fstat(fd, (err, stats) => {
    if (err) return
    console.log(stats)
    fs.close(fd) // 关闭文件
  })
})

打印结果如下:

image.png

操作文件夹

创建文件夹

fs 模块还能对文件夹进行操作,比如 fs.mkdir() 就可以创建文件夹,mkdir 可以看成是 make directory 的缩写:

fs.mkdir('./test', err => console.log(err)) // 创建 test 文件夹

读取文件夹

fs.readdir() 可以用于读取文件夹,在回调函数中会返回读取到的文件夹中所包含的文件或文件夹,比如 node 目录下的结构如下图所示:

image.png

执行如下代码对 node 目录进行读取:

// fs.js
fs.readdir('../node', (err, files) => {
  if (!err) console.log(files)
})

打印结果为:[ 'fs.js', 'test', 'test.txt' ]。 如果想获取更详细的信息,可以传入配置对象,将 withFileTypes 设置为 true

fs.readdir('../node', { withFileTypes: true }, (err, files) => {
  if (!err) console.log(files)
})

那么打印的结果就会通过 Symbol(type) 的值来表明是文件(值为 1),还是文件夹(值为 2),关于 Symbol 的介绍可参看 Symbol 详解

image.png

现在还有个问题,就是 test 文件夹里的 index.txt 文件没有被读取到,如果想读取 node 目录下的所有文件,可以通过递归读取:

function readDirRecursive(path) {
  fs.readdir(path, { withFileTypes: true }, (err, files) => {
    if (err) return
    files.forEach(item => {
      if (item.isDirectory()) {
        readDirRecursive(`${path}/${item.name}`)
      } else {
        console.log(item.name)
      }
    })
  })
}
readDirRecursive('../node')

我们也需要将 withFileTypes 设置为 true,这样返回的 files 数组中的元素才是一个个对象,并且有 name 属性和 isDirectory() 方法可以判断是否为文件夹。

重命名文件夹/文件

fs.rename() 可以重命名文件夹或文件,第 1 个参数为旧名称,第 2 个参数传新名称即可:

fs.rename('./test', './juejin', err => console.log(err))

感谢.gif 点赞.png
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情