文件系统
Node.js 文件系统(fs)模块详解
Node.js 的 fs 模块提供了与文件系统交互的 API,几乎涵盖了所有标准文件操作。它支持三种风格的 API:同步、回调式异步 和 Promise 式异步。下面详细介绍这些 API 的选择策略、流式读写、文件监视以及常用操作。
一、同步 / 异步 / Promise API 选择
fs 模块的每个操作都有三种形态,例如读取文件:fs.readFileSync()、fs.readFile()、fs.promises.readFile()。
1. 同步 API(Sync)
- 特点:阻塞事件循环,直到操作完成。易于编写和理解,但没有并发能力。
- 适用场景:初始化加载配置、脚本工具、CLI 程序等不需要高并发的场景。
- 示例:
const data = fs.readFileSync("./file.txt", "utf8");
console.log(data);
2. 回调式异步 API
- 特点:非阻塞,利用 Node.js 事件循环,需要提供回调函数处理结果。是 Node.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+ 稳定),推荐优先使用。
- 示例:
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)
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)
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)复制文件
将可读流直接导向可写流,实现高效复制:
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.pipeline 或 fs.promises 的 copyFile 对于简单复制更简单。
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(基于操作系统事件,效率高)
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(轮询,开销较大)
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)
// 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. 删除文件
fs.unlink('./fileToDelete.txt', (err) => { ... });
// Promises
await fs.unlink('./fileToDelete.txt');
3. 重命名 / 移动文件
fs.rename('./oldName.txt', './newName.txt', (err) => { ... });
// 也可用于移动文件到不同目录
await fs.rename('./data.txt', '../backup/data.txt');
4. 权限管理(chmod)
修改文件权限(类似 Unix chmod 命令):
// 数字模式:0o644 表示 rw-r--r--
fs.chmod('./file.txt', 0o644, (err) => { ... });
// 符号模式 Node.js 不直接支持,需用数字或相应对照
await fs.chmod('./file.txt', 0o755);
其他相关 API:fs.stat 获取当前权限,fs.access 检查权限。
5. 软链接(符号链接)与硬链接
- 软链接:类似于快捷方式,可以跨文件系统。
fs.symlink('./target.txt', './link.txt', (err) => { ... });
// 读取链接指向的路径
const realPath = await fs.readlink('./link.txt');
- 硬链接:多个文件名指向同一个 inode,不能跨文件系统。
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.readFile | fs.promises.readFile | fs.readFileSync |
| 写入文件 | fs.writeFile | fs.promises.writeFile | fs.writeFileSync |
| 追加内容 | fs.appendFile | fs.promises.appendFile | fs.appendFileSync |
| 删除文件 | fs.unlink | fs.promises.unlink | fs.unlinkSync |
| 重命名 | fs.rename | fs.promises.rename | fs.renameSync |
| 修改权限 | fs.chmod | fs.promises.chmod | fs.chmodSync |
| 创建软链接 | fs.symlink | fs.promises.symlink | fs.symlinkSync |
| 创建硬链接 | fs.link | fs.promises.link | fs.linkSync |
| 监视变化 | fs.watch | 无 | 无 |
最佳实践:
- 对于大文件处理,始终使用流(
createReadStream/createWriteStream)。 - 对于简单操作,优先选择
fs.promisesAPI 配合async/await。 - 注意使用
try/catch或catch处理 Promise 拒绝。 - 文件监视时,考虑防抖/节流以减少频繁回调。
推荐参考资料
- Node.js 官方文档 File system
- 深入理解流式 API: Stream API 指南
- 实用工具:
fs-extra库(提供了更友好的 Promise API 和额外方法)
相关文章
缓冲区
Buffer 是 Node.js 全局对象,用于处理二进制数据流(如文件、网络数据)。在 ES6 引入 TypedArray 之前,Buffer 是 Node.js 处理二进制的主要方式;现在 Buffer 实现了 Uint8Arra…
路径处理
path 模块提供了用于处理和转换文件路径的实用工具。它是 Node.js 核心模块,无需安装即可直接使用。由于不同操作系统(Windows、Linux、macOS)的路径分隔符不同(Windows 使用反斜杠 \,POSIX 使用正…
进程与子进程
Node.js 在单个进程中运行,但通过 process 对象、child_process 模块及 worker_threads 模块提供了对进程的精细控制以及多进程/多线程能力。下面从三个方面展开。
网络编程
Node.js 提供了丰富的内置模块,用于构建网络应用。下面从 HTTP/HTTPS、WebSocket、TCP/UDP 以及 DNS 解析四个方面进行深入介绍。
流
流是 Node.js 中处理有序数据的抽象接口,用于以连续方式异步读取或写入数据。它们特别适合处理大文件、网络通信或任何可能无限产生的数据。Node.js 内置的 stream 模块提供了四种基本流类型,以及用于组合和错误处理的工具函数。
其他常用模块
以下是 Node.js 中几个重要内置模块的详细介绍,涵盖事件触发器、定时器、加密、压缩、操作系统信息和实用工具。