文件系统操作

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.watchfs.watchFile 更高效(操作系统原生事件 vs 轮询)
  • recursive: true 选项支持递归创建目录

Why — 为什么

适用场景:

  • 配置文件读写:JSON/YAML 配置
  • 日志文件管理:写入和轮转
  • 文件上传下载:临时文件处理
  • 模板渲染:读取模板文件

对比读写方式:

维度readFilecreateReadStreamreadFileSync
内存占用文件大小固定(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}`);
}

常见问题与踩坑

问题原因解决方案
大文件 OOMreadFile 一次性加载用 Stream 流式处理
路径不存在报错未检查目录存在性mkdir({ recursive: true }) 先创建
临时文件未清理进程异常退出finallyunlink,或用 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;⑥ 病毒扫描大文件。


相关链接: