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