源私有文件系统
**注意:** 此功能在 Web 工作线程 中可用。
**源私有文件系统** (OPFS) 是作为 文件系统 API 的一部分提供的存储端点,它对页面的来源是私有的,对用户来说不可见,就像常规文件系统一样。它提供对一种特殊类型的文件的访问,这种文件在性能方面高度优化,并提供对其内容的原位写入访问。
使用文件系统访问 API 操作文件
文件系统访问 API 扩展了 文件系统 API,通过选择器方法提供对文件的访问。例如
Window.showOpenFilePicker()
允许用户选择要访问的文件,这将导致返回一个FileSystemFileHandle
对象。FileSystemFileHandle.getFile()
用于访问文件的內容,使用FileSystemFileHandle.createWritable()
/FileSystemWritableFileStream.write()
修改内容。FileSystemHandle.requestPermission({mode: 'readwrite'})
用于请求用户保存更改的权限。- 如果用户接受权限请求,更改将保存回原始文件。
这有效,但有一些限制。这些更改正在对用户可见的文件系统进行,因此有很多安全检查到位(例如,Chrome 中的 安全浏览)以防止恶意内容写入该文件系统。这些写入不是原位的,而是使用临时文件。除非通过所有安全检查,否则不会修改原始文件。
因此,这些操作相当慢。在进行小的文本更新时,这并不明显,但在进行更大的、大规模的文件更新(如 SQLite 数据库修改)时,性能会下降。
OPFS 如何解决这些问题?
OPFS 提供低级、逐字节的文件访问,这对页面的来源是私有的,对用户来说不可见。因此,它不需要相同的安全检查和权限授予系列,因此比文件系统访问 API 调用更快。它还具有一组可用的同步调用(其他文件系统 API 调用是异步的),这些调用只能在 Web 工作线程中运行,以免阻塞主线程。
总结一下 OPFS 与用户可见文件系统的区别
- OPFS 受到 浏览器存储配额限制 的约束,就像任何其他来源分区的存储机制一样(例如 IndexedDB API)。您可以通过
navigator.storage.estimate()
访问 OPFS 使用的存储空间量。 - 清除站点的存储数据会删除 OPFS。
- 访问 OPFS 中的文件不需要权限提示和安全检查。
- 浏览器会将 OPFS 的内容持久化到磁盘的某个位置,但您无法期望找到与之一一对应的创建的文件。OPFS 不打算对用户可见。
如何访问 OPFS?
要首先访问 OPFS,您可以调用 navigator.storage.getDirectory()
方法。这将返回对表示 OPFS 根目录的 FileSystemDirectoryHandle
对象的引用。
从主线程操作 OPFS
从主线程访问 OPFS 时,您将使用异步的、基于 Promise
的 API。您可以通过在表示 OPFS 根目录(以及创建的子目录)的 FileSystemDirectoryHandle
对象上分别调用 FileSystemDirectoryHandle.getFileHandle()
和 FileSystemDirectoryHandle.getDirectoryHandle()
来访问文件 (FileSystemFileHandle
) 和目录 (FileSystemDirectoryHandle
) 句柄。
**注意:** 将 { create: true }
传递到上面的方法会导致文件或文件夹在不存在时被创建。
// Create a hierarchy of files and folders
const fileHandle = await opfsRoot.getFileHandle("my first file", {
create: true,
});
const directoryHandle = await opfsRoot.getDirectoryHandle("my first folder", {
create: true,
});
const nestedFileHandle = await directoryHandle.getFileHandle(
"my first nested file",
{ create: true },
);
const nestedDirectoryHandle = await directoryHandle.getDirectoryHandle(
"my first nested folder",
{ create: true },
);
// Access existing files and folders via their names
const existingFileHandle = await opfsRoot.getFileHandle("my first file");
const existingDirectoryHandle =
await opfsRoot.getDirectoryHandle("my first folder");
读取文件
- 进行
FileSystemDirectoryHandle.getFileHandle()
调用以返回一个FileSystemFileHandle
对象。 - 调用
FileSystemFileHandle.getFile()
对象以返回一个File
对象。这是一种特殊类型的Blob
,因此可以像处理任何其他Blob
一样对其进行操作。例如,您可以直接通过Blob.text()
访问文本内容。
写入文件
- 进行
FileSystemDirectoryHandle.getFileHandle()
调用以返回一个FileSystemFileHandle
对象。 - 调用
FileSystemFileHandle.createWritable()
以返回一个FileSystemWritableFileStream
对象,这是一种特殊类型的WritableStream
。 - 使用
FileSystemWritableFilestream.write()
调用将内容写入它。 - 使用
WritableStream.close()
关闭流。
删除文件或文件夹
您可以在父目录上调用 FileSystemDirectoryHandle.removeEntry()
,将要删除的项目的名称传递给它。
directoryHandle.removeEntry("my first nested file");
您还可以在表示要删除的项目的 FileSystemFileHandle
或 FileSystemDirectoryHandle
上调用 FileSystemHandle.remove()
。要删除包括所有子文件夹的文件夹,请传递 { recursive: true }
选项。
await fileHandle.remove();
await directoryHandle.remove({ recursive: true });
以下提供了一种快速清除整个 OPFS 的方法
await (await navigator.storage.getDirectory()).remove({ recursive: true });
列出文件夹的内容
FileSystemDirectoryHandle
是一个 异步迭代器。因此,您可以使用 for await...of
循环和标准方法(如 entries()
、values()
和 keys()
)对其进行迭代。
例如
for await (let [name, handle] of directoryHandle) {
}
for await (let [name, handle] of directoryHandle.entries()) {
}
for await (let handle of directoryHandle.values()) {
}
for await (let name of directoryHandle.keys()) {
}
从 Web 工作线程操作 OPFS
Web 工作线程不会阻塞主线程,这意味着您可以在这种情况下使用同步文件访问 API。同步 API 速度更快,因为它们避免了处理 Promise 的需要。
您可以通过在常规的 FileSystemFileHandle
上调用 FileSystemFileHandle.createSyncAccessHandle()
来同步访问文件
**注意:** 尽管名称中包含“Sync”,但 createSyncAccessHandle()
方法本身是异步的。
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle("my highspeed file.txt", {
create: true,
});
const syncAccessHandle = await fileHandle.createSyncAccessHandle();
返回的 FileSystemSyncAccessHandle
上提供了许多同步方法
getSize()
:返回文件的大小(以字节为单位)。write()
:将缓冲区的内容写入文件,可选地在给定偏移量处写入,并返回写入的字节数。检查返回的写入字节数,允许调用者检测和处理错误和部分写入。read()
:将文件的内容读入缓冲区,可选地在给定偏移量处读入。truncate()
:将文件大小调整为给定的大小。flush()
:确保文件内容包含通过write()
完成的所有修改。close()
:关闭访问句柄。
以下是一个使用上述所有方法的示例
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle("fast", { create: true });
const accessHandle = await fileHandle.createSyncAccessHandle();
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
// Initialize this variable for the size of the file.
let size;
// The current size of the file, initially `0`.
size = accessHandle.getSize();
// Encode content to write to the file.
const content = textEncoder.encode("Some text");
// Write the content at the beginning of the file.
accessHandle.write(content, { at: size });
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `9` (the length of "Some text").
size = accessHandle.getSize();
// Encode more content to write to the file.
const moreContent = textEncoder.encode("More content");
// Write the content at the end of the file.
accessHandle.write(moreContent, { at: size });
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `21` (the length of
// "Some textMore content").
size = accessHandle.getSize();
// Prepare a data view of the length of the file.
const dataView = new DataView(new ArrayBuffer(size));
// Read the entire file into the data view.
accessHandle.read(dataView, { at: 0 });
// Logs `"Some textMore content"`.
console.log(textDecoder.decode(dataView));
// Read starting at offset 9 into the data view.
accessHandle.read(dataView, { at: 9 });
// Logs `"More content"`.
console.log(textDecoder.decode(dataView));
// Truncate the file after 4 bytes.
accessHandle.truncate(4);
另请参阅
- 源私有文件系统 在 web.dev 上