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.File 与 IOUtils¶
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 开始被认为是功能完整的。