A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://developer.cdn.mozilla.net/zh-CN/docs/Web/API/File_System_API/Origin_private_file_system below:

源私有文件系统 - Web API | MDN

源私有文件系统

Baseline 2023

Newly available

安全上下文: 此项功能仅在一些支持的浏览器的安全上下文(HTTPS)中可用。

备注: 此特性在 Web Worker 中可用。

源私有文件系统(OPFS)是作为文件系统 API 的一部分提供的一个存储端点。它是页面所属的源专用的,并且不像常规文件系统那样对用户可见。它提供了对一种特殊类型文件的访问能力,这种文件经过高度性能优化,并提供对其内容的原地写入访问特性。

使用文件系统访问 API 处理文件

扩展自文件系统 API 的文件系统访问 API 使用选择器提供了对文件的访问能力。例如:

  1. Window.showOpenFilePicker() 允许用户选择一个文件用于访问,文件将作为结果以一个 FileSystemFileHandle 对象的形式被返回。
  2. 调用 FileSystemFileHandle.getFile() 以访问文件的内容,使用 FileSystemFileHandle.createWritable() 或 FileSystemWritableFileStream.write() 来修改内容。
  3. 调用 FileSystemHandle.requestPermission({mode: 'readwrite'}) 来请求用户的权限以保存更改。
  4. 如果用户接受了权限请求,更改就会保存回原文件。

这个方法可行,但是有一些限制。这些更改是对用户可见的文件系统进行的,所以会有很多适当的安全性检查(比方说 Chrome 的安全浏览)来防止恶意内容被写入到文件系统。这些写入不是原地的,会先写入到一个临时文件。除非通过了所有的安全性检查,否则原文件不会被修改。

因此,这些操作会相当缓慢。在你进行小规模的文本更新时没那么明显,但是当进行像 SQLite 数据库更改这样的更显著、更大规模的文件更新时就会遇到性能问题。

OPFS 是怎么解决这些问题的?

OPFS 提供了页面所属源私有的、对用户不可见的、底层的逐字节文件访问能力。因此它不需要经过与调用文件系统访问 API 所需的一系列相同的安全性检查和授权,而且比文件系统访问 API 更快。它还有一套同步调用方法可用(其他的文件系统 API 调用是异步的),但只能在 web worker 中运行,这样就不会阻塞主线程。

概括 OPFS 和用户可见文件系统的不同:

如何访问 OPFS?

想要访问 OPFS,你首先要调用 navigator.storage.getDirectory() 方法。返回一个代表 OPFS 根目录的 FileSystemDirectoryHandle 对象的引用。

在主线程中操作 OPFS

在主线程中访问 OPFS 时,你要使用基于 Promise 的异步 API。你可以调用代表 OPFS 根目录(以及其中被创建的子目录)的 FileSystemDirectoryHandle 对象上的 FileSystemDirectoryHandle.getFileHandle() 和 FileSystemDirectoryHandle.getDirectoryHandle() 方法来分别访问文件(FileSystemFileHandle)和目录(FileSystemDirectoryHandle)。

备注: 在上述方法中传入 { create: true } 会在文件或文件夹不存在时创建相应的文件或文件夹。

// 创建层级结构的文件和文件夹
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 },
);

// 通过文件名和文件夹名访问已有的文件和文件夹
const existingFileHandle = await opfsRoot.getFileHandle("my first file");
const existingDirectoryHandle =
  await opfsRoot.getDirectoryHandle("my first folder");
读取文件
  1. 调用 FileSystemDirectoryHandle.getFileHandle() 以返回一个 FileSystemFileHandle 对象。
  2. 调用 FileSystemFileHandle.getFile() 方法返回一个 File 对象。这是一种特化的 Blob 对象,所以可以像操作其他 Blob 对象那样去操作它。比如,你可以通过 Blob.text() 直接访问其文本内容。
写入文件 删除文件或目录

你可以在父目录上调用 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 worker 中操作 OPFS

Web Worker 不会阻塞主线程,这意味着你可以在其上下文中使用同步文件访问 API。同步的 API 因其不需要处理 promise,所以更快。

你可以通过在常规的 FileSystemFileHandle 上调用 FileSystemFileHandle.createSyncAccessHandle() 来同步地处理文件:

备注: 虽然 createSyncAccessHandle() 的名称带有“Sync(同步)”字眼,但是这个方法本身是异步的。

const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle("my-high-speed-file.txt", {
  create: true,
});
const syncAccessHandle = await fileHandle.createSyncAccessHandle();

返回的 FileSystemSyncAccessHandle 上有几个同步的方法可用:

这里是一个使用了上述所有方法的示例:

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();

// 将这个变量初始化为文件的大小。
let size;
// 文件当前的大小,最开始是 `0`。
size = accessHandle.getSize();
// 编码要写入文件的内容。
const content = textEncoder.encode("Some text");
// 在文件的开头写入内容。
accessHandle.write(content, { at: size });
// 强制刷入更改。
accessHandle.flush();
// 文件当前的大小,现在是 `9`(“Some text”的长度)。
size = accessHandle.getSize();

// 编码更多要写入文件的内容。
const moreContent = textEncoder.encode("More content");
// 在文件的末尾写入内容。
accessHandle.write(moreContent, { at: size });
// 强制刷入更改。
accessHandle.flush();
// 文件当前的大小,现在是 `21`(“Some textMore content”的长度)。
size = accessHandle.getSize();

// 准备一个长度与文件相同的数据视图。
const dataView = new DataView(new ArrayBuffer(size));

// 将整个文件读取到数据视图。
accessHandle.read(dataView, { at: 0 });
// 打印 `"Some textMore content"`。
console.log(textDecoder.decode(dataView));

// 在数据视图中的偏移位置 9 处开始读取。
accessHandle.read(dataView, { at: 9 });
// 打印 `"More content"`。
console.log(textDecoder.decode(dataView));

// 裁去文件头 4 个字节之后的内容。
accessHandle.truncate(4);
参见

RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4