第 6 章 在 Node.js 中操作文件系统

围绕路径解析、文件描述符、原子替换、元数据和流式背压组织文件 I/O,并把不存在、权限和部分写入当成正常分支。

直觉起点

、、、、共同构成本页的观察坐标。围绕路径解析、文件描述符、原子替换、元数据和流式背压组织文件 I/O,并把不存在、权限和部分写入当成正常分支。 Node.js 的关键不只是异步 API,而是谁创建资源、谁接收完成通知、错误沿哪条边传播、什么条件下进程可以安全退出。2014 年版接口与现代 Node 的差异会明确标注,不用新 API 冒充原书目录,也不把废弃接口直接推荐给新项目。

六阶段执行链

1. 规范化路径

固定 Node 版本、启动参数、环境变量和输入,先预测同步调用栈、异步资源与最终输出。没有版本证据的示例无法区分书中历史行为与当前运行时。

2. 选择同步边界

明确数据是字符串、字节、流、报文、请求、事件还是数据库记录,并标出所有者。创建资源的代码必须同时设计 error、timeout、abort 或 close 路径。

3. 打开资源

只保留最小处理器和一个完成出口。回调、Promise 与事件不得为同一操作重复签发结果;监听器注册顺序和一次性语义要能从代码读出。

4. 读写或管道

注入一个边界:空输入、长输入、半包、慢消费者、权限拒绝、数据库回滚或客户端断开。停止在首个偏离点,保存错误对象、资源状态与未完成数量。

5. 提交原子结果

检查旧接口在现代 Node 的状态:仍支持、语义变化、弃用或移出核心。现代替代必须保持原章节要解决的问题,同时说明迁移不能自动保证的兼容行为。

6. 关闭并核验

清除故障并重放同一输入,等待文件、套接字、连接池和子进程排空。退出码、最终响应和资源计数全部收敛,才算恢复而不是暂时停止报错。

核心机制深挖

围绕路径解析、文件描述符、原子替换、元数据和流式背压组织文件 I/O,并把不存在、权限和部分写入当成正常分支。 所有案例统一记录五类证据:输入表示、调度边界、资源所有者、错误出口、关闭条件。输入表示错误会造成编码、分帧或查询歧义;调度边界错误会把同步事件误认为异步回调;所有权不清会留下描述符和连接;错误出口重复会产生二次响应;关闭条件缺失会让进程一直存活或强行丢数据。

本页签发不变量是:路径根、资源关闭、部分失败与提交边界都明确;大文件不因一次性读入导致内存随输入线性失控。 代码必须能回答正常路径完成了什么、边界输入限制在哪里、失败由谁观察、取消怎样传播、关闭等待哪些资源,以及 2014 年 API 在当前版本中是否仍适合新代码。只展示成功日志或最终页面,不能证明这些条件。

版本兼容账本

原书出版于 2014 年,示例所处的 Node、npm、Express、Socket.IO 与数据库驱动生态都早于当前版本。重构保留原书问题与模块身份,但代码采用安全、可运行的现代形式,并为 domain、punycode、旧 Buffer 构造器和旧框架 API 标记历史状态。读者迁移旧项目时应先固定原版本复现,再逐层替换接口与测试,不能一次升级所有依赖后凭报错猜测语义变化。

兼容账本至少包含:原书接口、当时用途、当前支持状态、推荐替代、行为差异和删除旧兼容层的条件。核心原理例如 TCP 无消息边界、流需要背压、事务需要回滚不会因版本改变;具体函数签名、默认超时、模块入口和错误类型则可能变化。把稳定原理与易变 API 分开,才能既忠实复刻知识体系,又不误导今天的新代码。

出版社目录逐项讲解

第6章 在Node.js中操作文件系统

围绕路径解析、文件描述符、原子替换、元数据和流式背压组织文件 I/O,并把不存在、权限和部分写入当成正常分支。

实验从“规范化路径”开始:固定 Node 版本、入口、输入与资源上限,先预测事件、字节或消息怎样流动,再只注入一个超时、断线、权限、背压或版本变化。记录首个偏离点,删除故障后重放同一输入,不能用额外监听器和重复重试掩盖根因。

6.1 同步方法与异步方法

同步 fs 方法阻塞主线程,适合启动阶段的小型、确定工作;请求路径应使用 Promise、回调或流。异步不等于无限并发,仍需限制同时打开文件数。

实验从“选择同步边界”开始:固定 Node 版本、入口、输入与资源上限,先预测事件、字节或消息怎样流动,再只注入一个超时、断线、权限、背压或版本变化。记录首个偏离点,删除故障后重放同一输入,不能用额外监听器和重复重试掩盖根因。

6.2 对文件执行读写操作

读取要处理不存在、权限、短读和编码,写入要考虑部分失败与覆盖。关键配置采用临时文件、fsync 需求评估和 rename 提交,避免中途崩溃留下半文件。

实验从“打开资源”开始:固定 Node 版本、入口、输入与资源上限,先预测事件、字节或消息怎样流动,再只注入一个超时、断线、权限、背压或版本变化。记录首个偏离点,删除故障后重放同一输入,不能用额外监听器和重复重试掩盖根因。

6.3 创建与读取目录

mkdir 的 recursive 选项可建立层级,readdir 可返回 Dirent 减少额外 stat。目录内容在遍历期间可能变化,不能先检查再假设后续操作仍安全。

实验从“读写或管道”开始:固定 Node 版本、入口、输入与资源上限,先预测事件、字节或消息怎样流动,再只注入一个超时、断线、权限、背压或版本变化。记录首个偏离点,删除故障后重放同一输入,不能用额外监听器和重复重试掩盖根因。

6.4 查看与修改文件或目录的信息

stat/lstat 区分跟随符号链接与链接本身,权限、时间和类型都可能在操作间变化。安全逻辑尽量基于已打开的描述符,避免检查与使用之间的竞态。

实验从“提交原子结果”开始:固定 Node 版本、入口、输入与资源上限,先预测事件、字节或消息怎样流动,再只注入一个超时、断线、权限、背压或版本变化。记录首个偏离点,删除故障后重放同一输入,不能用额外监听器和重复重试掩盖根因。

6.5 可以对文件或目录执行的其他操作

重命名、删除、截断、链接和监视各有平台差异。rename 的原子性只在特定文件系统边界成立,watch 事件也可能合并或丢失,业务一致性不能只靠文件通知。

实验从“关闭并核验”开始:固定 Node 版本、入口、输入与资源上限,先预测事件、字节或消息怎样流动,再只注入一个超时、断线、权限、背压或版本变化。记录首个偏离点,删除故障后重放同一输入,不能用额外监听器和重复重试掩盖根因。

6.6 使用文件流

Readable 和 Writable 通过 pipe 或 pipeline 传递数据与背压。pipeline 能统一传播错误和关闭;手工监听 data 并无条件 write 会在慢目标下累积内存。

实验从“规范化路径”开始:固定 Node 版本、入口、输入与资源上限,先预测事件、字节或消息怎样流动,再只注入一个超时、断线、权限、背压或版本变化。记录首个偏离点,删除故障后重放同一输入,不能用额外监听器和重复重试掩盖根因。

6.7 对路径进行操作

path.join 组合片段,resolve 产生绝对路径,normalize 只做语法归一。处理用户路径必须先固定允许根目录并验证解析结果仍在根内,不能只删除两点字符串。

实验从“选择同步边界”开始:固定 Node 版本、入口、输入与资源上限,先预测事件、字节或消息怎样流动,再只注入一个超时、断线、权限、背压或版本变化。记录首个偏离点,删除故障后重放同一输入,不能用额外监听器和重复重试掩盖根因。

6.8 小结

本章小结不是 API 清单,而是复核“规范化路径、选择同步边界、打开资源、读写或管道、提交原子结果、关闭并核验”是否闭环:输入从哪里进入、资源由谁持有、错误怎样传播、何时释放,以及现代运行时是否改变了原书接口的支持状态。

实验从“打开资源”开始:固定 Node 版本、入口、输入与资源上限,先预测事件、字节或消息怎样流动,再只注入一个超时、断线、权限、背压或版本变化。记录首个偏离点,删除故障后重放同一输入,不能用额外监听器和重复重试掩盖根因。

资源与失败矩阵

资源正常完成常见边界失败出口关闭证据
Buffer 或消息完整编码、解析空、截断、超长校验错误无越界读写
文件或流读写、管道结束权限、慢目标pipeline 拒绝描述符关闭
套接字或请求响应一次半包、断线、超时error 或 abortclose 已观察
子进程或连接池退出或归还stderr、回滚非零码或异常无悬挂资源

最小可运行实验

import { writeFile, rename } from "node:fs/promises";
import path from "node:path";
 
const target = path.resolve("data", "state.json");
const temporary = target + ".tmp";
await writeFile(temporary, JSON.stringify({ ok: true }), "utf8");
await rename(temporary, target);
book: Node.js 权威指南
page: ndg-06-filesystem
edition: 2014
sample: normal | boundary | failure | recovery
resource_owner_known: true
single_completion: true
close_observed: true
freeze node version + entry + input
predict sync stack + async resources
change exactly one boundary condition
stop at first divergent state
remove fault and replay
wait for every resource to close

常见误区与故障注入

四类样本与验收

样本注入方式必查证据通过条件
正常小输入、可用依赖事件、字节、响应、提交只完成一次
边界空、长、慢、并发上限、背压、顺序内存与延迟受控
失败权限、断线、超时、回滚首个错误与所有者错误不丢不重复
恢复删除故障后重放close、exit、连接数无悬挂资源

目录证据:第6章 在Node.js中操作文件系统、6.1 同步方法与异步方法、6.2 对文件执行读写操作、6.3 创建与读取目录、6.4 查看与修改文件或目录的信息、6.5 可以对文件或目录执行的其他操作、6.6 使用文件流、6.7 对路径进行操作、6.8 小结。本页按公开目录独立重构,不复制原书正文;每个条目都进入输入、机制、故障、版本和关闭证据链。

练习

小结

  • fs/promises:对应“规范化路径”的核心观察量。
  • 文件描述符:对应“选择同步边界”的核心观察量。
  • 路径:对应“打开资源”的核心观察量。
  • 文件流:对应“读写或管道”的核心观察量。
  • 原子替换:对应“提交原子结果”的核心观察量。
  • 9 个本页目录条目已全部映射到执行链与版本账本。
  • 最终输出只是证据之一,资源关闭和错误传播同样决定是否通过。

术语表

讨论

评论区加载中…