File System Access API 实战:浏览器端本地文件读写架构

技术架构(更新于 2026年6月15日)

浏览器文件访问的演进

方案 读写能力 持久化 权限模型 性能
<input type="file"> 只读
File + FileReader 只读
IndexedDB 读写字节 同源 低(序列化)
File System Access 读写 用户授权
OPFS 读写 同源沙箱 最高

File System Access API 让浏览器真正具备了本地应用的文件读写能力。


文件选择器:showOpenFilePicker / showSaveFilePicker

打开文件

const [fileHandle] = await window.showOpenFilePicker({
  types: [{
    description: '图片文件',
    accept: { 'image/*': ['.png', '.jpg', '.webp'] },
  }],
  multiple: false,
});

const file = await fileHandle.getFile();
const arrayBuffer = await file.arrayBuffer();

保存文件

const handle = await window.showSaveFilePicker({
  suggestedName: 'compressed.webp',
  types: [{
    description: 'WebP 图片',
    accept: { 'image/webp': ['.webp'] },
  }],
});

const writable = await handle.createWritable();
await writable.write(compressedBlob);
await writable.close();

FileSystemFileHandle:读写与权限

读取文件内容

const fileHandle: FileSystemFileHandle = handle;
const file = await fileHandle.getFile();

// 多种读取方式
const text = await file.text();           // 文本
const buffer = await file.arrayBuffer();  // 二进制
const stream = file.stream();             // 流式读取

写入文件

async function writeFile(handle: FileSystemFileHandle, content: Blob | string | BufferSource) {
  const writable = await handle.createWritable();
  await writable.write(content);
  await writable.close();
}

权限查询与请求

async function verifyPermission(handle: FileSystemFileHandle, readWrite = true) {
  const options: FileSystemHandlePermissionDescriptor = { mode: readWrite ? 'readwrite' : 'read' };

  if ((await handle.queryPermission(options)) === 'granted') return true;
  if ((await handle.requestPermission(options)) === 'granted') return true;

  return false;
}

权限模型:首次通过 picker 获取的 handle 自动获得读权限。写权限需要用户显式授权(requestPermission 会弹出提示)。


目录访问与遍历

const dirHandle = await window.showDirectoryPicker();

for await (const [name, handle] of dirHandle.entries()) {
  if (handle.kind === 'file') {
    const file = await handle.getFile();
    console.log(`文件: ${name}, 大小: ${file.size}`);
  } else if (handle.kind === 'directory') {
    console.log(`目录: ${name}`);
  }
}

递归查找文件

async function* findFiles(dirHandle: FileSystemDirectoryHandle, pattern: RegExp) {
  for await (const [name, handle] of dirHandle.entries()) {
    if (handle.kind === 'file' && pattern.test(name)) {
      yield { name, handle };
    } else if (handle.kind === 'directory') {
      yield* findFiles(handle, pattern);
    }
  }
}

// 查找所有 PDF 文件
for await (const pdf of findFiles(dirHandle, /\.pdf$/)) {
  console.log(pdf.name);
}

Origin Private File System (OPFS)

OPFS 是浏览器为每个源提供的私有沙箱文件系统,无需用户授权

const opfsRoot = await navigator.storage.getDirectory();

// 创建文件
const fileHandle = await opfsRoot.getFileHandle('work-data.bin', { create: true });

// 创建子目录
const subDir = await opfsRoot.getDirectoryHandle('cache', { create: true });

// 删除文件
await opfsRoot.removeEntry('work-data.bin');

OPFS vs IndexedDB

特性 IndexedDB OPFS
存储类型 结构化数据 文件(二进制)
读写方式 事务 + 序列化 直接文件 I/O
大文件性能 差(序列化开销) 优秀
随机访问 ✅(Access Handle)
容量 同源限制 同源限制
Worker 支持

Access Handle:高性能随机读写

OPFS 文件支持 createSyncAccessHandle(),提供同步、随机位置的文件读写——性能接近原生文件系统

const fileHandle = await opfsRoot.getFileHandle('large-data.bin', { create: true });
const accessHandle = await fileHandle.createSyncAccessHandle();

// 写入数据到指定位置
const writeBuffer = new Uint8Array([1, 2, 3, 4, 5]);
accessHandle.write(writeBuffer, { at: 0 });

// 读取指定位置的数据
const readBuffer = new Uint8Array(5);
accessHandle.read(readBuffer, { at: 0 });

// 获取文件大小
const fileSize = accessHandle.getSize();

// 刷新到磁盘
accessHandle.flush();

// 关闭句柄
accessHandle.close();

关键优势:Access Handle 是同步的,可在 Worker 中使用,无需 async/await,性能比 WritableStream 快 10-100 倍。


实战:PDF 合并的文件读写架构

工具库 PDF 合并 利用 File System Access API 实现"打开→处理→保存"的完整流程:

async function mergePdfWorkflow() {
  // 1. 用户选择多个 PDF
  const handles = await window.showOpenFilePicker({
    types: [{ accept: { 'application/pdf': ['.pdf'] } }],
    multiple: true,
  });

  // 2. 读取所有文件
  const buffers: ArrayBuffer[] = [];
  for (const handle of handles) {
    const file = await handle.getFile();
    buffers.push(await file.arrayBuffer());
  }

  // 3. 合并处理
  const mergedPdf = await mergePdfs(buffers);

  // 4. 保存结果
  const saveHandle = await window.showSaveFilePicker({
    suggestedName: 'merged.pdf',
    types: [{ accept: { 'application/pdf': ['.pdf'] } }],
  });
  const writable = await saveHandle.createWritable();
  await writable.write(mergedPdf);
  await writable.close();
}

持久化文件句柄

通过 IndexedDB 保存 handle,下次打开时恢复访问:

// 保存句柄
async function saveHandle(key: string, handle: FileSystemFileHandle) {
  const db = await openDB('file-handles', 1, {
    upgrade(db) { db.createObjectStore('handles'); },
  });
  await db.put('handles', handle, key);
}

// 恢复句柄
async function loadHandle(key: string) {
  const db = await openDB('file-handles', 1);
  const handle: FileSystemFileHandle = await db.get('handles', key);

  // 验证权限
  if (await verifyPermission(handle, true)) {
    return handle;
  }
  return null;
}

常见问题

File System Access API 的浏览器支持?

Chrome/Edge 86+ 完整支持。Firefox 和 Safari 未支持。需要做特性检测和降级处理。

OPFS 的存储容量限制?

OPFS 与 Cache API、IndexedDB 共享同源存储配额(通常为可用磁盘空间的 50%+)。可通过 navigator.storage.estimate() 查询。

Access Handle 能在主线程用吗?

可以,但同步 I/O 会阻塞主线程。最佳实践是在 Worker 中使用 Access Handle,避免阻塞 UI。

如何处理大文件写入?

使用 WritableStream 的分块写入,避免一次性加载整个文件到内存:

const writable = await handle.createWritable();
for await (const chunk of readableStream) {
  await writable.write(chunk);
}
await writable.close();

总结

File System Access API 让浏览器具备了本地应用级的文件读写能力。showOpenFilePicker/showSaveFilePicker 提供用户授权的文件访问,FileSystemFileHandle 管理读写权限,OPFS 提供无需授权的沙箱存储,Access Handle 实现高性能同步随机读写。这是构建浏览器端文件处理工具的核心基础设施。

本站提供浏览器本地工具,免注册即可试用 →

#File System Access#OPFS#文件句柄#本地文件#读写权限