菜单

文件系统

相关源文件

Node.js 的文件系统 (fs) 模块提供了一个与文件系统交互的接口,其模型参照了标准的 POSIX 函数。该模块是 Node.js 的核心部分,支持在本地文件系统上读取、写入和操作文件和目录。

概述

fs 模块公开了三种不同的文件系统操作 API 风格

  1. 基于回调的 API - 使用回调的传统 Node.js 异步 API
  2. 同步 API - 直接返回结果并抛出错误的阻塞操作
  3. 基于 Promise 的 API - 返回 Promise 的现代异步 API

所有文件系统操作在每种风格中都有对应的方法,为开发人员提供了代码结构上的灵活性。

FS 模块架构

来源: lib/fs.js1-1169 lib/internal/fs/promises.js1-150 src/node_file.cc1-200 src/node_dir.cc1-100

API 接口

三种 API 风格

Node.js 提供了三种不同的方式来与文件系统交互

来源: lib/fs.js172-508 doc/api/fs.md66-122

API 使用示例

每种 API 风格都有其使用模式

Promise 示例

Callback 示例

Synchronous 示例

来源: doc/api/fs.md37-121

文件句柄 (File Handles)

FileHandle 类提供了一种面向对象的方式来处理文件描述符。这个抽象主要用于基于 Promise 的 API,但它也构成了 Node.js 中所有文件操作的基础。

FileHandle 架构

来源: lib/internal/fs/promises.js158-420 src/node_file.cc220-665

FileHandle 类代表文件系统中的一个打开的文件。它提供了各种文件操作的方法

  • 读取文件(readreadvreadFile
  • 写入文件(writewritevwriteFile
  • 文件信息(stat
  • 控制操作(closesyncdatasynctruncate
  • 创建流(createReadStreamcreateWriteStreamreadableWebStream

FileHandle 会维护自己的生命周期,如果它们在未显式关闭的情况下被垃圾回收,会发出警告。它们还支持 SymbolAsyncDispose 符号,以便在现代 JavaScript 中与 using await 结构一起使用。

来源: lib/internal/fs/promises.js158-420 src/node_file.cc330-390

目录操作

目录操作提供了读取目录内容和遍历目录的方法

关键目录类和方法

来源: lib/internal/fs/dir.js35-282 src/node_dir.cc100-245

目录 API 允许使用以下功能遍历目录内容

  • 同步和异步方法
  • 异步迭代支持,使用 for await...of
  • 通过 Dirent 对象获取文件类型信息
  • 通过 close() 方法正确清理资源
  • 可选的递归目录遍历

使用异步迭代的示例

来源: lib/internal/fs/dir.js260-282 test/parallel/test-fs-opendir.js45-66

文件元数据

fs 模块提供了几个类来表示文件元数据

Stats 和 Dirent 类

来源: lib/internal/fs/utils.js160-215 lib/internal/fs/utils.js380-543

StatsDirent 类都提供了确定文件系统条目类型的方法。主要区别在于

  • Statsstat()lstat()fstat() 操作返回
  • DirentDir.read() 等目录迭代方法返回
  • Stats 包含详细信息,如大小、时间戳、权限
  • Dirent 主要包含名称和类型信息
  • 两者都有等效的方法,例如 isFile()isDirectory()

来源: lib/internal/fs/utils.js380-543 lib/internal/fs/utils.js160-215

流式文件 I/O

Node.js 为高效处理大文件提供了基于流的接口,用于文件 I/O

文件流

来源: lib/internal/fs/streams.js162-830

文件流建立在 Node.js 的流抽象之上,以提供高效的文件 I/O

  • 可读流(fs.createReadStream()),用于读取文件
  • 可写流(fs.createWriteStream()),用于写入文件
  • 支持文件描述符编号和 FileHandle 对象
  • 可配置的缓冲区大小和字节范围
  • 自动资源管理
  • 与字符串和缓冲区处理的集成

FileHandle 实例还提供创建流的便捷方法

来源: lib/internal/fs/promises.js356-377 lib/internal/fs/streams.js162-240

Web Streams 集成

Node.js 提供 Web Streams API 集成,以实现现代流接口

来源: lib/internal/fs/promises.js285-341

FileHandle.readableWebStream() 方法从 WHATWG Web Streams API 创建面向字节的 ReadableStream,从而可以更轻松地与 Web 平台 API 和现代 JavaScript 构造(如 ReadableStream.pipeThrough())集成。

实现细节

请求封装

fs 模块使用几个包装类来管理异步操作

来源: src/node_file.cc80-95 lib/fs.js80-95 lib/internal/fs/promises.js447-465

请求封装系统提供:

  • 异步操作完成的回调
  • 错误处理和传播
  • C++ 和 JavaScript 之间的上下文保留
  • 资源跟踪和清理
  • 跟踪和调试支持

线程池利用

文件系统操作在主线程之外执行,以避免阻塞

来源: doc/api/fs.md142-148 lib/fs.js80-95

线程池实现的关键特性

  • 文件操作在 libuv 线程池中运行,以避免阻塞主事件循环
  • 同步操作会阻塞 JavaScript 线程,直到完成
  • 基于 Promise 的操作使用与回调操作相同的底层线程池
  • 线程池操作不同步且非线程安全
  • 对同一文件的多个并发修改可能导致数据损坏

性能考量

在选择不同的 API 样式时,请考虑这些性能影响

API 样式性能特征
同步阻塞 JavaScript 执行直到完成,代码最简单,但可能导致应用程序冻结
回调执行时间和内存的最高性能,但错误处理更复杂
Promise现代代码风格,错误处理更好,与回调相比有轻微的开销

来源: doc/api/fs.md92-94

其他性能考虑因素

  • 文件读取对不同文件大小有特殊优化
  • 大文件写入被分解为块,以防止内存问题
  • 对于高性能 I/O,流在内存使用和吞吐量之间提供了最佳平衡
  • FileHandle 缓存可以提高对同一文件的重复操作的性能
  • 递归目录操作可能会消耗更多内存

来源: lib/internal/fs/promises.js507-596 lib/internal/fs/promises.js472-505

跨平台兼容性

fs 模块抽象了操作系统差异,但仍存在一些特定于平台的行为

  • 路径分隔符会自动处理(POSIX 上为 /,Windows 上为 \ 或 /)
  • 某些文件类型(FIFO、套接字、块设备)在 Windows 上不可用
  • 符号链接行为在不同平台之间存在差异,在某些情况下 Windows 需要管理员权限
  • 文件权限模型在 POSIX 和 Windows 之间存在差异
  • 最大路径长度因平台而异
  • 文件名的大小写敏感性因平台和文件系统而异

来源: lib/internal/fs/utils.js359-377 lib/internal/fs/utils.js537-543

常见文件操作

以下是使用基于 Promise 的 API 进行常见文件操作的快速参考

操作方法描述
读取文件readFile()读取整个文件内容
写入文件writeFile()将数据写入文件,创建或替换
检查是否存在access()检查文件是否存在且可访问
文件信息stat()获取文件元数据
文件操作rename()truncate()重命名文件或调整其大小
文件移除unlink()删除文件
目录创建mkdir()创建目录
目录读取opendir()读取目录内容
目录移除rmdir()移除目录
符号链接操作symlink()readlink()创建和读取符号链接

来源: doc/api/fs.md866-1016

结论

Node.js 文件系统模块提供了一套全面的 API 来处理文件系统。通过其三种 API 样式(回调、同步和基于 Promise),它为不同的编程模式提供了灵活性,同时保持了功能的一致性。

该实现利用 libuv 的线程池在主线程之外执行 I/O 操作,确保 Node.js 应用程序即使在执行繁重的文件操作时也能保持响应。FileHandle 抽象为文件操作提供了一种面向对象的方法,而流接口则允许高效地处理大文件。

了解 fs 模块的架构和性能特征有助于开发人员就为特定用例选择哪种 API 样式和方法做出明智的决定。