文件系统操作
What — 是什么
文件系统操作是 Node.js 与磁盘交互的基础能力,
fs模块提供同步/异步/流式三种文件操作模式,覆盖文件读写、目录管理、权限控制和文件监控。
核心概念:
- fs/promises:Promise 版本的 fs API,推荐使用
- 同步 vs 异步:
readFileSync阻塞事件循环,readFile非阻塞 - Stream 读写:
createReadStream/createWriteStream流式处理大文件 - 文件监控:
fs.watch/fs.watchFile监听文件变更 - 权限:
fs.access/fs.chmod/fs.stat检查和修改文件权限 - 临时文件:
os.tmpdir()获取临时目录,用后fs.unlink清理
关键特性:
fs/promises是现代 Node.js 推荐的 fs 使用方式- 大文件必须用 Stream,不能一次性加载到内存
fs.watch比fs.watchFile更高效(操作系统原生事件 vs 轮询)recursive: true选项支持递归创建目录
Why — 为什么
适用场景:
- 配置文件读写:JSON/YAML 配置
- 日志文件管理:写入和轮转
- 文件上传下载:临时文件处理
- 模板渲染:读取模板文件
对比读写方式:
| 维度 | readFile | createReadStream | readFileSync |
|---|---|---|---|
| 内存占用 | 文件大小 | 固定(chunk) | 文件大小 |
| 阻塞 | 否 | 否 | 是 |
| 适用 | 小文件 | 大文件 | 启动时配置 |
How — 怎么用
代码示例
const fs = require('fs/promises');
const path = require('path');
// 读写 JSON 配置
async function loadConfig(filePath) {
const data = await fs.readFile(filePath, 'utf8');
return JSON.parse(data);
}
async function saveConfig(filePath, config) {
await fs.writeFile(filePath, JSON.stringify(config, null, 2));
}
// 递归遍历目录
async function walkDir(dir, callback) {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) await walkDir(fullPath, callback);
else await callback(fullPath);
}
}
// 文件监控
const watcher = fs.watch('./src', { recursive: true });
for await (const event of watcher) {
console.log(`${event.eventType}: ${event.filename}`);
}
常见问题与踩坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 大文件 OOM | readFile 一次性加载 | 用 Stream 流式处理 |
| 路径不存在报错 | 未检查目录存在性 | mkdir({ recursive: true }) 先创建 |
| 临时文件未清理 | 进程异常退出 | finally 中 unlink,或用 tmp-promise 库 |
fs.watch 重复触发 | 编辑器保存触发多次 | 防抖处理 |
最佳实践
- 使用
fs/promises,不用回调版本 - 大文件用 Stream 处理
- 目录创建加
{ recursive: true } - 临时文件用后立即清理
面试题
Q1: fs.watch 和 fs.watchFile 的区别?
fs.watch基于操作系统原生事件(inotify/FSEvents),高效但有平台差异(事件类型和触发次数不同)。fs.watchFile基于轮询(定期 stat 比较时间戳),稳定但开销大。推荐fs.watch,需要稳定时用chokidar库。
Q2: 如何安全地处理用户上传的文件?
安全措施:① 验证文件类型(检查 magic number 而非扩展名);② 限制文件大小;③ 生成随机文件名(
crypto.randomUUID()),不用原始文件名;④ 存储到专门目录,不与代码同目录;⑤ 设置权限0o600;⑥ 病毒扫描大文件。
相关链接: