FORMA

文件系统

Node.js 文件系统(fs)模块详解

Node.js 的 fs 模块提供了与文件系统交互的 API,几乎涵盖了所有标准文件操作。它支持三种风格的 API:同步回调式异步Promise 式异步。下面详细介绍这些 API 的选择策略、流式读写、文件监视以及常用操作。

一、同步 / 异步 / Promise API 选择

fs 模块的每个操作都有三种形态,例如读取文件:fs.readFileSync()fs.readFile()fs.promises.readFile()

1. 同步 API(Sync)

  • 特点:阻塞事件循环,直到操作完成。易于编写和理解,但没有并发能力。
  • 适用场景:初始化加载配置、脚本工具、CLI 程序等不需要高并发的场景。
  • 示例
js
const data = fs.readFileSync("./file.txt", "utf8");
console.log(data);

2. 回调式异步 API

  • 特点:非阻塞,利用 Node.js 事件循环,需要提供回调函数处理结果。是 Node.js 传统风格。
  • 适用场景:兼容旧代码,或需要精细控制错误处理时。
  • 示例
js
fs.readFile("./file.txt", "utf8", (err, data) => {
    if (err) throw err;
    console.log(data);
});

3. Promise API(fs.promises

  • 特点:返回 Promise,支持 async/await,使得异步代码更清晰、易于组合。
  • 适用场景:现代 Node.js 开发(Node.js 10+ 稳定),推荐优先使用。
  • 示例
js
import { promises as fs } from "fs";
async function read() {
    const data = await fs.readFile("./file.txt", "utf8");
    console.log(data);
}

选择建议

  • 新项目优先使用 fs.promises + async/await,代码可读性好且不易出错。
  • 工具脚本或初始化可使用同步 API,简单直接。
  • 需要高性能且回调嵌套较多的遗留代码,可考虑用 util.promisify 包装回调函数。

二、流式读写(createReadStream / createWriteStream

对于大文件,一次性将整个文件读入内存会导致内存溢出或性能下降。流式读写以小块数据分批处理,适合大文件复制、日志处理、文件上传等场景。

1. 可读流(createReadStream

js
const readStream = fs.createReadStream("./largeFile.txt", {
    encoding: "utf8",
    highWaterMark: 64 * 1024, // 每次读取 64KB
});
readStream.on("data", chunk => {
    console.log(`Received ${chunk.length} bytes`);
});
readStream.on("end", () => console.log("No more data"));
readStream.on("error", err => console.error(err));

2. 可写流(createWriteStream

js
const writeStream = fs.createWriteStream("./output.txt");
writeStream.write("Hello\n");
writeStream.write("World\n");
writeStream.end("Done");
writeStream.on("finish", () => console.log("Write completed"));

3. 管道(pipe)复制文件

将可读流直接导向可写流,实现高效复制:

js
const src = fs.createReadStream("./source.txt");
const dest = fs.createWriteStream("./dest.txt");
src.pipe(dest);
src.on("error", console.error);
dest.on("finish", () => console.log("Copy finished"));

4. 高级:pipeline 方法(处理错误自动清理)

推荐使用 stream.pipelinefs.promisescopyFile 对于简单复制更简单。

js
const { pipeline } = require("stream");
const { promisify } = require("util");
const pipelineAsync = promisify(pipeline);
await pipelineAsync(src, dest);

注意:对于普通文件复制,fs.copyFile 比手动流更高效,但流式读写适用于需要处理数据(如压缩、加密)的场景。

三、监视文件变化

Node.js 提供了两种监视文件/目录变化的方法:fs.watch(推荐)和 fs.watchFile(轮询)。

1. fs.watch(基于操作系统事件,效率高)

js
const watcher = fs.watch(
    "./someDir",
    { recursive: true },
    (eventType, filename) => {
        console.log(`事件类型: ${eventType}`);
        if (filename) console.log(`文件名: ${filename}`);
    }
);
watcher.on("error", err => console.error(err));
// 停止监视
watcher.close();
  • eventType'rename'(文件移动/重命名/删除)或 'change'(内容修改)。
  • 参数 recursive:是否监视子目录(需平台支持,macOS 支持,Linux 需额外配置)。
  • 注意filename 可能为 null,且某些系统可能不提供完整路径。

2. fs.watchFile(轮询,开销较大)

js
fs.watchFile("./file.txt", { interval: 1000 }, (curr, prev) => {
    if (curr.mtime !== prev.mtime) {
        console.log("文件被修改");
    }
});
fs.unwatchFile("./file.txt"); // 停止监视
  • 使用轮询检查文件状态,适合无法使用 watch 的网络文件系统(NFS)。
  • 性能较差,不推荐在性能敏感场景使用。

选择:优先使用 fs.watch,它更高效且实时。如果 watch 不可用或需要跨平台一致性,可回退到 watchFile

四、常用操作

1. 读写文件(基本)

  • 异步读取fs.readFile(path, options, callback)
  • 同步读取fs.readFileSync(path, options)
  • 写入文件fs.writeFile(path, data, options, callback)(覆盖写入)
  • 追加写入fs.appendFile(path, data, callback)
js
// Promise 示例
await fs.writeFile("./hello.txt", "Hello Node.js");
await fs.appendFile("./hello.txt", "\nNew line");
const content = await fs.readFile("./hello.txt", "utf8");

2. 删除文件

js
fs.unlink('./fileToDelete.txt', (err) => { ... });
// Promises
await fs.unlink('./fileToDelete.txt');

3. 重命名 / 移动文件

js
fs.rename('./oldName.txt', './newName.txt', (err) => { ... });
// 也可用于移动文件到不同目录
await fs.rename('./data.txt', '../backup/data.txt');

4. 权限管理(chmod

修改文件权限(类似 Unix chmod 命令):

js
// 数字模式:0o644 表示 rw-r--r--
fs.chmod('./file.txt', 0o644, (err) => { ... });
// 符号模式 Node.js 不直接支持,需用数字或相应对照
await fs.chmod('./file.txt', 0o755);

其他相关 API:fs.stat 获取当前权限,fs.access 检查权限。

5. 软链接(符号链接)与硬链接

  • 软链接:类似于快捷方式,可以跨文件系统。
js
fs.symlink('./target.txt', './link.txt', (err) => { ... });
// 读取链接指向的路径
const realPath = await fs.readlink('./link.txt');
  • 硬链接:多个文件名指向同一个 inode,不能跨文件系统。
js
fs.link('./original.txt', './hardlink.txt', (err) => { ... });
  • 判断是否为软链接:通过 fs.lstat 获取文件信息,stats.isSymbolicLink() 返回 true

6. 目录操作

  • 创建目录fs.mkdir(path, { recursive: true }, callback)recursive 可创建多级目录)
  • 读取目录内容fs.readdir(path, (err, files) => {...})
  • 删除空目录fs.rmdir(path)(需为空)
  • 删除非空目录:可使用 fs.rm(path, { recursive: true, force: true })(Node.js 14+)

总结表

操作类型异步回调Promise同步
读取文件fs.readFilefs.promises.readFilefs.readFileSync
写入文件fs.writeFilefs.promises.writeFilefs.writeFileSync
追加内容fs.appendFilefs.promises.appendFilefs.appendFileSync
删除文件fs.unlinkfs.promises.unlinkfs.unlinkSync
重命名fs.renamefs.promises.renamefs.renameSync
修改权限fs.chmodfs.promises.chmodfs.chmodSync
创建软链接fs.symlinkfs.promises.symlinkfs.symlinkSync
创建硬链接fs.linkfs.promises.linkfs.linkSync
监视变化fs.watch

最佳实践

  • 对于大文件处理,始终使用流(createReadStream / createWriteStream)。
  • 对于简单操作,优先选择 fs.promises API 配合 async/await
  • 注意使用 try/catchcatch 处理 Promise 拒绝。
  • 文件监视时,考虑防抖/节流以减少频繁回调。

推荐参考资料

  • Node.js 官方文档 File system
  • 深入理解流式 API: Stream API 指南
  • 实用工具:fs-extra 库(提供了更友好的 Promise API 和额外方法)

Series

core

2 / 7