IOUtils 迁移指南

通过新的文件 API 提升性能


什么是 IOUtils?

IOUtils 是一个用于在 Firefox 前端执行文件 I/O 的特权 JavaScript API。它被开发为 OS.File 的替代品,用于解决 bug 1231711。它**不应与**非特权的 DOM File API 混淆。

IOUtils 提供了一个最小的 API 表面,可以通过受 OS.File 启发的静态方法集合来执行常见的 I/O 任务。它在 C++ 中实现,并通过 WebIDL 绑定公开给 JavaScript。

最新的 API 始终可以在 IOUtils.webidl 中找到。

OS.File 的区别

IOUtils 具有与 OS.File 类似的 API,但需要注意一些关键区别。

没有 File 实例(工作线程中的 SyncReadFile 除外)

大多数 IOUtils 方法仅对绝对路径字符串进行操作,并且不会向调用方公开文件句柄。此规则的例外是 openFileForSyncReading API,它仅在工作线程中可用。

此外,OS.File 通过 fd 属性公开了特定于平台的文件描述符。IOUtils 不公开文件描述符。

WebIDL 没有 Date 类型

IOUtils 使用 C++ 编写,并通过 WebIDL 公开给 JavaScript。许多 OS.File 的用法都涉及获取或操作文件元数据,例如上次修改时间,但是 Date 类型在 WebIDL 中不存在。使用 IOUtils,这些值将以自 1970-01-01T00:00:00Z 以来以毫秒为单位的数字形式返回给调用方。如果需要,可以安全地从这些值构造 Date

例如,要获取文件的上次修改时间并将其更新为当前时间

let { lastModified } = await IOUtils.stat(path);

let lastModifiedDate = new Date(lastModified);

let now = new Date();

await IOUtils.touch(path, now.valueOf());

某些方法未实现

由于各种原因(复杂性、安全性、底层系统调用的可用性、实用性等),以下 OS.File 方法在 IOUtils 中没有对应的模拟。它们也**不会**被实现。

  • void unixSymlink(in string targetPath, in string createPath)

  • string getCurrentDirectory(void)

  • void setCurrentDirectory(in string path)

  • object open(in string path)

  • object openUnique(in string path)

错误以 DOMException 的形式报告

OS.File 方法遇到错误时,它将抛出/拒绝自定义的 OS.File.Error。这些对象具有可以检查常见错误情况的自定义属性。

IOUtils 具有类似的行为,但是其方法始终拒绝 DOMException,其名称取决于错误

异常名称 异常原因
NotFoundError 在磁盘上找不到指定路径的文件。
NotAllowedError 操作系统拒绝访问指定路径的文件。
NotReadableError 由于某种原因无法读取指定路径的文件。它可能太大而无法读取,或者已损坏,或者其他原因。异常消息应包含更多详细信息。
ReadOnlyError 指定路径的文件为只读,无法修改。
NoModificationAllowedError 指定路径上已存在文件,并且根据指定的选项无法覆盖。异常消息应包含更多详细信息。
OperationError I/O 操作期间发生错误。例如,未能分配缓冲区。异常消息应包含更多详细信息。
UnknownError 在实现中发生未知错误。异常消息中应包含 nsresult 错误代码,以帮助调试和改进 IOUtils 内部错误处理。

IOUtils 主要为异步 API

OS.File 为主线程使用者提供了异步前端,并为工作线程提供了同步前端。IOUtils 仅为其大部分 API 表面提供了异步 API。这些异步方法可以从主线程和特权 chrome 工作线程中调用。

此规则的唯一例外是 openFileForSyncReading,它允许在工作线程中同步读取文件。

OS.FileIOUtils

OS.File 的一些方法和选项在 IOUtils 中保留了相同的名称和底层行为,但其他一些方法已重命名。以下是每个 API 中方法和选项的详细比较以及示例。

读取文件

IOUtils 提供以下方法从文件读取数据。与 OS.File 一样,它们接受一个 options 字典。

注意:可以读取的最大文件大小为 UINT32_MAX 字节。尝试读取更大的文件将导致 NotReadableError

Promise<Uint8Array> read(DOMString path, ...);

Promise<DOMString> readUTF8(DOMString path, ...);

Promise<any> readJSON(DOMString path, ...);

// Workers only:
SyncReadFile openFileForSyncReading(DOMString path);

选项

OS.File 选项 IOUtils 选项 描述
bytes: number? maxBytes: number? 如果指定,则仅读取最多这么多字节。否则,读取整个文件。默认值为 null。
compression: 'lz4' decompress: boolean 如果为 true,则读取文件并返回解压缩的 LZ4 流。否则,只逐字节读取文件。默认值为 false。
encoding: 'utf-8' N/A;请改用 readUTF8 将文件解释为 UTF-8 编码的文本,并返回一个字符串给调用方。

示例

读取原始(无符号)字节值

OS.File

let bytes = await OS.File.read(path); // Uint8Array

IOUtils

let bytes = await IOUtils.read(path); // Uint8Array
读取 UTF-8 编码的文本

OS.File

let utf8 = await OS.File.read(path, { encoding: 'utf-8' }); // string

IOUtils

let utf8 = await IOUtils.readUTF8(path); // string
读取 JSON 文件

IOUtils

let obj = await IOUtils.readJSON(path); // object
读取 LZ4 压缩的文件内容

OS.File

// Uint8Array
let bytes = await OS.File.read(path, { compression: 'lz4' });
// string
let utf8 = await OS.File.read(path, {
    encoding: 'utf-8',
    compression: 'lz4',
});

IOUtils

let bytes = await IOUtils.read(path, { decompress: true }); // Uint8Array
let utf8 = await IOUtils.readUTF8(path, { decompress: true }); // string
从工作线程同步读取文件片段到缓冲区

OS.File

// Read 64 bytes at offset 128, workers only:
let file = OS.File.open(path, { read: true });
file.setPosition(128);
let bytes = file.read({ bytes: 64 }); // Uint8Array
file.close();

IOUtils

// Read 64 bytes at offset 128, workers only:
let file = IOUtils.openFileForSyncReading(path);
let bytes = new Uint8Array(64);
file.readBytesInto(bytes, 128);
file.close();

写入文件

IOUtils 提供以下方法将数据写入文件。与 OS.File 一样,它们接受一个 options 字典。

Promise<unsigned long long> write(DOMString path, Uint8Array data, ...);

Promise<unsigned long long> writeUTF8(DOMString path, DOMString string, ...);

Promise<unsigned long long> writeJSON(DOMString path, any value, ...);

选项

OS.File 选项 IOUtils 选项 描述
backupTo: string? backupFile: string? 标识在执行写入操作之前要备份的目标文件的路径。如果未指定,则不会执行备份。默认值为 null。
tmpPath: string? tmpPath: string? 标识要首先写入的路径,然后再执行移动以覆盖目标文件。如果未指定,则将直接写入目标文件。默认值为 null。
noOverwrite: boolean mode: 'overwrite' 或 'create' 'create' 模式将拒绝覆盖现有文件。默认值为 'overwrite'。
flush: boolean flush: boolean 如果为 true,则强制操作系统将其内部缓冲区刷新到磁盘。默认值为 false。
encoding: 'utf-8' N/A;请改用 writeUTF8 允许调用方提供一个字符串,该字符串将在磁盘上编码为 utf-8 文本。

示例

写入原始(无符号)字节值

OS.File

let bytes = new Uint8Array();
await OS.File.writeAtomic(path, bytes);

IOUtils

let bytes = new Uint8Array();
await IOUtils.write(path, bytes);
写入 UTF-8 编码的文本

OS.File

let str = "";
await OS.File.writeAtomic(path, str, { encoding: 'utf-8' });

IOUtils

let str = "";
await IOUtils.writeUTF8(path, str);
写入 JSON 对象

IOUtils

let obj = {};
await IOUtils.writeJSON(path, obj);
使用 LZ4 压缩写入

OS.File

let bytes = new Uint8Array();
await OS.File.writeAtomic(path, bytes, { compression: 'lz4' });
let str = "";
await OS.File.writeAtomic(path, str, {
    compression: 'lz4',
});

IOUtils

let bytes = new Uint8Array();
await IOUtils.write(path, bytes, { compress: true });
let str = "";
await IOUtils.writeUTF8(path, str, { compress: true });

移动文件

IOUtils 提供以下方法在磁盘上移动文件。与 OS.File 一样,它接受一个 options 字典。

Promise<void> move(DOMString sourcePath, DOMString destPath, ...);

选项

OS.File 选项 IOUtils 选项 描述
noOverwrite: boolean noOverwrite: boolean 如果为 true,则如果目标文件已存在则失败。默认值为 false。
noCopy: boolean N/A;不会实现 此选项在 IOUtils 中未实现,如果提供将被忽略

示例

OS.File

await OS.File.move(srcPath, destPath);

IOUtils

await IOUtils.move(srcPath, destPath);

删除文件

IOUtils 提供了一种删除磁盘上文件的方法。 OS.File 提供了多种方法。

Promise<void> remove(DOMString path, ...);

选项

OS.File 选项 IOUtils 选项 描述
ignoreAbsent: 布尔值 ignoreAbsent: 布尔值 如果为真,并且目标文件不存在,则不引发错误。默认为真。
不适用;OS.File 有专门的方法用于删除目录 recursive: 布尔值 如果为真,并且目标是目录,则递归删除该目录及其所有子目录。默认为假。

示例

删除文件

OS.File

await OS.File.remove(path, { ignoreAbsent: true });

IOUtils

await IOUtils.remove(path);
删除目录及其所有内容

OS.File

await OS.File.removeDir(path, { ignoreAbsent: true });

IOUtils

await IOUtils.remove(path, { recursive: true });
删除空目录

OS.File

await OS.File.removeEmptyDir(path); // Will throw an exception if `path` is not empty.

IOUtils

await IOUtils.remove(path); // Will throw an exception if `path` is not empty.

创建目录

IOUtils 提供以下方法在磁盘上创建目录。与 OS.File 一样,它接受一个选项字典。

Promise<void> makeDirectory(DOMString path, ...);

选项

OS.File 选项 IOUtils 选项 描述
ignoreExisting: 布尔值 ignoreExisting: 布尔值 如果为真,即使目标目录已存在,也会成功。默认为真。
from: 字符串 createAncestors: 布尔值 如果为真,则 IOUtils 将创建路径中所有缺失的祖先目录。默认为真。此选项与 OS.File 不同,后者要求调用方指定一个根路径,从中创建缺失的目录。
unixMode: 数字 permissions: 无符号长整数 创建目录时使用的文件模式。在 Windows 上被忽略。默认为 0755。
winSecurity 不适用 IOUtils 不支持在 Windows 上设置自定义目录安全设置。

示例

OS.File

await OS.File.makeDir(srcPath, destPath);

IOUtils

await IOUtils.makeDirectory(srcPath, destPath);

更新文件的修改时间

IOUtils 提供以下方法来更新文件的修改时间。

Promise<void> setModificationTime(DOMString path, optional long long modification);

示例

OS.File

await OS.File.setDates(path, new Date(), new Date());

IOUtils

await IOUtils.setModificationTime(path, new Date().valueOf());

获取文件元数据

IOUtils 提供以下方法查询文件元数据。

Promise<void> stat(DOMString path);

示例

OS.File

let fileInfo = await OS.File.stat(path);

IOUtils

let fileInfo = await IOUtils.stat(path);

复制文件

IOUtils 提供以下方法来复制磁盘上的文件。与 OS.File 一样,它接受一个选项字典。

Promise<void> copy(DOMString path, ...);

选项

OS.File 选项 IOUtils 选项 描述
noOverwrite: boolean noOverwrite: boolean 如果为 true,则如果目标文件已存在则失败。默认值为 false。
不适用;OS.File 似乎不支持递归复制文件 recursive: 布尔值 如果为真,则递归复制源文件。

示例

复制文件

OS.File

await OS.File.copy(srcPath, destPath);

IOUtils

await IOUtils.copy(srcPath, destPath);
递归复制目录

OS.File

// Not easy to do.

IOUtils

await IOUtils.copy(srcPath, destPath, { recursive: true });

迭代目录

目前,IOUtils 还没有方法公开目录的迭代器。这受 bug 1577383 阻碍。作为此功能的权宜之计,可以使用以下方法获取目录的所有子项并遍历返回的路径数组。

Promise<sequence<DOMString>> getChildren(DOMString path);

示例

OS.File

for await (const { path } of new OS.FileDirectoryIterator(dirName)) {</p>
  ...
}

IOUtils

for (const path of await IOUtils.getChildren(dirName)) {
  ...
}

检查文件是否存在

IOUtils 提供以下方法,类似于同名的 OS.File 方法。

Promise<boolean> exists(DOMString path);

示例

OS.File

if (await OS.File.exists(path)) {
  ...
}

IOUtils

if (await IOUtils.exists(path)) {
  ...
}

设置文件的权限

IOUtils 提供以下方法,类似于同名的 OS.File 方法。

Promise<void> setPermissions(DOMString path, unsigned long permissions, optional boolean honorUmask = true);

选项

OS.File 选项 IOUtils 选项 描述
unixMode: 数字 permissions: 无符号长整数 表示权限的 UNIX 文件模式。在 IOUtils 中是必需的。
unixHonorUmask: 布尔值 honorUmask: 布尔值 如果省略或为真,则任何 UNIX 文件模式都会被权限修改。否则将应用权限的确切值。

示例

OS.File

await OS.File.setPermissions(path, { unixMode: 0o600 });

IOUtils

await IOUtils.setPermissions(path, 0o600);

常见问题

为什么我应该使用 IOUtils 而不是 OS.File

Bug 1231711 提供了一些很好的上下文,但一些原因包括

  • 减少缓存争用,

  • 更快的启动速度,以及

  • 更低的内存使用量。

此外,IOUtils 得益于本地实现,这有助于 Project Fission 的性能相关工作。

我们正在积极努力将 OS.File 的旧代码用法迁移到类似的 IOUtils 调用,因此目前不应引入新的 OS.File 用法。

我需要导入任何内容才能使用此 API 吗?

不需要!它可以通过 JavaScript 中的 IOUtils 全局变量访问(ChromeOnly 上下文)。

我可以在 C++ 或 Rust 中使用此 API 吗?

目前,用法专门针对 JavaScript 调用者,所有 C++ 方法都是私有的,除了 Web IDL 绑定。但是,如果兴趣足够大,应该很容易公开 C++ 和/或 Rust 的符合人体工程学的公共方法。

为什么 IOUtils 不是用 Rust 编写的?

在撰写本文时,与 Rust 相比,面向 C++ 的工具对 Web IDL 绑定的支持更加成熟。

IOUtils 是否功能完整?它什么时候可用?

IOUtils 从 Firefox 83 开始被认为是功能完整的。