🛡️ 《告别内存泄漏!WASM+OPFS极致内存管控方案揭秘(系列一 OPFS介绍)》

64 阅读2分钟

简介

    wasm(WebAssembly)和OPFS(Origin Private File System)是前端在进行科学计算和大文件处理时常用的技术,本系列就着重介绍怎么将两种技术组合来实现大文件计算的同时保持高效率和低内存效果。

OPFS介绍

    OPFS 全称为 Origin-Private File System(源私有文件系统),是浏览器提供的一种新型 Web API,允许网页在用户设备上创建和管理一个私有的、与网站源(Origin)绑定的虚拟文件系统。它是现代浏览器支持的标准技术,旨在解决 Web 应用对文件高效读写的需求(尤其是大文件和二进制数据)。
    通过Can I Use可以看到,目前除了IE,其他主流浏览器都已支持该特性,可以放心使用。

image.png     OPFS本身对于文件读写支持同步和异步两种模式,主线中使用时只可以使用异步,WebWorker中两种模式都可以正常使用。

文件导入示例

本期着重介绍怎么将用户文件写入到OPFS中。

1、获取文件对象

通过简单的<input type="file" />控件可以获取到文件对象。

2、将文件对象发送到独立线程中

const uploadFile = () => {
    const uploadFile = document.getElementById("file").files[0];
    if (!uploadFile) {
      alert("请选择需要上传的文件");
      return;
    }
    worker.postMessage({
      type: "copy",
      file: uploadFile,
    });
};

3、在线程中将文件切片保存到OPFS中

const copyFileToOPFS = async (file) => {
  Module.print("准备上传文件:", file.name);
  // 线程中可以直接使用同步文件读取
  const fileReader = new FileReaderSync();
  const filename = file.name;
  // 获取OPFS根目录
  const root = await navigator.storage.getDirectory();
  // 获取文件句柄
  const fileHandle = await root.getFileHandle(filename, {
    create: true, // 如果没有文件则创建一个新文件
  });
  // 获取文件同步写句柄,只可以在worker中使用
  const accessHandle = await fileHandle.createSyncAccessHandle();
  let times = 0;
  let index = 0;
  const length = 1024 * 1024 * 50;
  let flag = true;
  const fileSize = file.size;
  while (flag) {
    times++;
    if (times > 1000) {
      flag = false;
      break;
    }
    if (index >= fileSize) {
      flag = false;
      break;
    }
    const start = index;
    let end = index + length;
    if (end > fileSize) {
      end = fileSize;
    }
    // 获取文件切片
    const content = file.slice(start, end);
    // 读取为ArrayBuffer
    let arrayBuffer = fileReader.readAsArrayBuffer(content);
    // 将数据写入文件
    accessHandle.write(arrayBuffer, {
      at: start, // 文件内字节下标,主要用于分片写入文件
    });
    // 将数据刷入文件中,只有执行了flush数据才会真实保存到文件中
    accessHandle.flush();
    arrayBuffer = null;
    index = end;
    Module.print("文件已上传:", ((index / fileSize) * 100).toFixed(2), "%");
  }
  // 此处必须关闭文件句柄,不然会导致后续无法再次获取句柄
  accessHandle.close();
  Module.print("文件上传成功: " + file.name);
};

源码示例

github.com/yangyanggu/…