node - 内置模块 - fs

129 阅读6分钟

在 Node.js 中,提供了一个用于操作文件的模块,叫做 fs 模块(File System)。通过这个模块,我们可以读取文件内容,并将其返回给客户端。

  1. 读取文件资源「 用户数据、配置文件、图片、音频等 」并返回给客户端
  2. 应用于前端自动化工具中

操作文件

读取内容

import fs from 'node:fs/promises';

// const 文件内容 = fs.readFile(文件路径, 配置对象 | 文件编码)
// 1. 文件路径 => 必须提供文件路径。如果路径不存在,会直接抛出错误。
// 2. 第二个参数可以是一个配置对象,也可以是直接指定编码(可选)。
//    + 如果省略第二个参数,默认返回的是 Buffer 对象(即二进制数据流)。
//    + 如果指定编码(如 'utf-8'),返回的是字符串。
//    + 如果指定配置对象,可以设置更详细的选项,例如编码和文件访问模式(flag)。
const users = await fs.readFile('./assets/user.json', {
  encoding: 'utf-8', // 指定编码,返回字符串
  flag: 'r'          // 以只读模式打开文件
});

console.log(users);

配置对象

在 Node.js 的 fs 模块中,配置对象通常包含以下参数:

属性说明
encoding指定读取或写入文件时使用的字符编码。例如:'utf8'
flag文件的操作模式,决定文件的读写行为
flag

flag 参数用于指定文件操作的模式,以下是常用的 flag 值及其含义:

  • fs.readFile 的默认 flag'r'(只读模式)。
  • fs.writeFile 的默认 flag'w'(写入模式)。
Flag 值描述
'r'只读模式
+ 如果文件已存在,可以读取文件内容;
+ 如果文件不存在,会抛出异常。
'r+'读写模式
+ 如果文件已存在,可以读写文件内容;
+ 如果文件不存在,会抛出异常。
'w'写入模式
+ 如果文件已存在,会覆盖文件内容;
+ 如果文件不存在,会创建新文件。
'w+'读写模式
+ 如果文件已存在,可以读写文件内容(覆盖原内容);
+ 如果文件不存在,会创建新文件。
'a'追加模式
+ 如果文件已存在,写入的内容会追加到文件末尾;
+ 如果文件不存在,会创建新文件。
'a+'读写追加模式
+ 如果文件已存在,可以读写并追加内容;
+ 如果文件不存在,会创建新文件。

一般情况下

  • writeFile 只是用来新增内容,所以flag一般设置为aw即可
  • readFile 只是用来读取内容,所以flag一般设置为r皆可
  • open 是一个更底层的操作,允许开发者以多种模式打开文件。其具体行为取决于 flag 的设置。

文件描述符

当运行一个 Node.js 程序时,它会启动一个进程。在操作系统的底层,会维护一个文件和资源的映射表。每个文件在打开时,系统会为其分配一个唯一的标识符,这个标识符被称为文件描述符(File Descriptor,简称 FD)。

在 Node.js 中,文件描述符是一个非负整数,用于标识进程中打开的文件或资源。

import fs from 'node:fs/promises'

// 打开文件
// fs.open(path, flags, mode)
//  + path: 文件路径
//  + flags: 文件的打开方式
//    - 'r':只读模式(文件必须存在)
//    - 'w':写入模式(文件不存在会创建新文件,若文件存在会清空内容)
//    - 'a':追加模式(文件不存在会创建新文件,若文件存在会在末尾追加内容)
//  + mode: 文件权限(与 Linux 文件权限完全一致)

// fs.open 的返回值是一个文件句柄(FileHandle)
// 文件句柄是一个包含文件描述符和操作对应文件方法的 JavaScript 对象
const fileHandle = await fs.open('./assets/user.json', 'r')

// 读取文件内容
const content = await fileHandle.readFile('utf-8') // 使用文件句柄读取文件内容,指定编码为 UTF-8
console.log(content)

// 获取文件状态(如文件大小 {size}、创建时间 {birthtime}、修改时间 {mtime} 等)
const fileStatus = await fileHandle.stat()
console.log(fileStatus)

// 注意:通过文件描述符操作文件时,文件读取完毕后不会自动关闭,需要手动关闭文件句柄
await fileHandle.close()

写入内容

import fs from 'node:fs/promises';

const users = [
  { name: '张三', age: 20 },
];

// 参数一 => 文件路径(字符串形式)
// 参数二 => 写入的内容(必须是字符串)
// 参数三 => 配置对象(可选,包含写入选项,如 `flag`)
fs.writeFile('./assets/user.json', JSON.stringify(users, null, 2), {
  flag: 'w+',
});

操作目录

创建目录

在 Node.js 中,使用 fs 模块的 writeFilewriteFileSync 方法时,如果目标文件不存在,会自动新建文件。

但如果目标路径中的文件夹不存在,这些方法无法自动新建文件夹,需先使用 fs.mkdirfs.mkdirSync 创建文件夹。

这里的 mkdir 是 “make directory”的缩写,表示创建文件夹。如果创建失败,通常是因为权限问题。大多数情况下,只要有权限,创建文件夹是可以成功的。

import fs from 'node:fs/promises';

// 创建文件夹
// 1. 参数一 => 文件夹路径 「如果文件夹已存在,会直接抛出错误」
// 2. 参数二 => 配置对象 「可选」
//    + recursive => 如果中间文件夹不存在,是否自动创建
//      - 默认值为 false。假设不存在 assets,会直接抛出错误。
//      - 如果设置为 true,假设不存在 assets,会自动创建中间文件夹 assets 后再新建目录 test。
//    + mode => 文件夹权限 「默认值为 0o777」=> 一个八进制值,表示文件夹的权限。
await fs.mkdir('./assets/test', { recursive: true });

读取目录

import fs from 'node:fs/promises';

const files = await fs.readdir('./assets')

// `files` 是一个字符串数组,包含文件夹中的所有文件和子文件夹的名称
// 但是,这种读取方式无法区分每个名称是文件还是文件夹。
console.log(files) // [ 'bar', 'foo', 'index.js' ]
import fs from 'node:fs/promises';

const files = await fs.readdir('./assets', { withFileTypes: true })

// 如果我们需要区分文件和文件夹,可以使用 `fs.readdir` 的 `withFileTypes` 参数, 加上后就变成了一个对象数组
// 对象数组类似于 Dirent { name: 'bar', parentPath: './assets', [Symbol(type)]: 2 }
// + name => 文件名
// + parentPath => 文件路径
// + [Symbol(type)] => 文件类型
files.forEach(async (file) => {
  // `isDirectory()` 方法可以判断当前项是否是文件夹
  if (file.isDirectory()) {
    console.log(`${file.name}/`)
  } else {
    // `name` 属性则返回文件或文件夹的名称
    console.log(file.name)
  }
})
import fs from 'node:fs/promises';

// 如果文件夹中存在嵌套结构,比如子文件夹中还有文件夹,我们可以使用递归来读取所有内容
async function readDir(path) {
  const files = await fs.readdir(path, { withFileTypes: true })

  if (files.length) {
    files.forEach(async (file) => {
      if (file.isDirectory()) {
        console.log(`${path}/${file.name}`)
        await readDir(`${path}/${file.name}`)
      } else {
        console.log(`${path}/${file.name}`)
      }
    })
  }
}

readDir('./assets')

判断目录是否存在

import fs from 'node:fs'

// 早期 Node.js 使用同步方法 existsSync 判断文件 或 目录是否存在
// 该方法会返回一个布尔类型值,用于标识目标目录或文件是否存在
const isExists = fs.existsSync('./uploads')
console.log(isExists) // true
import fs from 'node:fs/promises'

try {
  // access 用于判断 文件 或 目录 是否存在
  // 如果存在,则不报错
  // 如果不存在,则报错
  await fs.access('./uploads')
} catch (error) {
  // 如果目录不存在,则创建目录
  await fs.mkdir('./uploads')
}

重命名

import fs from 'node:fs/promises';

// 重命名操作,源文件不存在会报错

// 重命名文件夹
fs.rename('./assets/bar', './assets/bar-new')

// 重命名文件
fs.rename('./assets/file1.txt', './assets/foo-new.md')