JavaScript FileReader Slice 性能

29

我正在尝试使用JavaScript中的FileApi来访问文本文件的前几行。

为了实现这个目标,我从文件开头剪切任意数量的字节,并将blob传递给FileReader

对于大型文件,这需要很长时间,尽管我目前的理解是只需要访问文件的前几个字节。

  • 在后台是否有一些必须在对文件进行片段操作之前先访问整个文件的实现方式?
  • 这取决于FileApi的浏览器实现吗?

我目前已在Chrome和Edge (chromium)中测试过。

Chrome中使用性能开发工具进行分析显示,在reader.onloadend之前存在很多空闲时间,且内存使用没有增加。不过,这可能是因为FileApi是在浏览器本身中实现的,因此不会反映在JavaScript性能统计数据中。

我的FileReader实现看起来像这样:

const reader = new FileReader();

reader.onloadend = (evt) => {
  if (evt.target.readyState == FileReader.DONE) {
    console.log(evt.target.result.toString());
  }
};

// Slice first 10240 bytes of the file
var blob = files.item(0).slice(0, 1024 * 10);

// Start reading the sliced blob
reader.readAsBinaryString(blob);

这个方法运作得很好,但正如描述的那样,对于大文件表现不佳。我尝试了10kb、100mb和6gb的文件。似乎直到第一个10kb被记录的时间与文件大小直接相关。

有关于如何提高读取文件开头性能的任何建议吗?


编辑: 使用@BenjaminGruenbaum建议的响应流和DOM流,可悲地不能提高读取性能。

var dest = newWritableStream({​​​​​​​​
    write(str) {​​​​​​​​
        console.log(str);
    }​​​​​​​​,
}​​​​​​​​);
var blob = files.item(0).slice(0, 1024 * 10);

(blob.stream ? blob.stream() : newResponse(blob).body)
// Decode the binary-encoded response to string
  .pipeThrough(newTextDecoderStream())
  .pipeTo(dest)
  .then(() => {​​​​​​​​
      console.log('done');
  }​​​​​​​​);


2
嘿,使用Response和DOM流有帮助吗?我不确定为什么在这里使用readAsBinarySring很慢,因为在blob上使用.slice应该只读取您想要的部分 - 但是您所描述的情况表明确实正在等待整个文件。 - Benjamin Gruenbaum
在我的macOS上,使用SSD驱动器无法重现此问题。您能否准确说明您的操作步骤和测量方法?您的文件存储在哪里?当您使用内存中的数据(new Blob([await file.arrayBuffer()]))时会发生什么?浏览器在首次访问文件时必须对其进行“快照”,但我认为通常只使用lastModified字段,尽管对于较大的文件,您的操作系统可能需要更长时间来访问文件的元数据。 - Kaiido
4
那么FileReader与此无关?为什么不在问题中明确说明?对我来说,这听起来只是你的操作系统需要花费所有这些时间来触及文件并生成元数据。我恐怕slice()不能改变这一点。至于为什么您的操作系统将所需时间与文件大小相关联,我不知道。也许值得在其他环境中测试,使用其他硬盘、其他文件系统等。 - Kaiido
我认为大部分时间都花在了“读取”步骤之前。你可以考虑专注于“获取文件”的过程。 - 小聪聪到此一游
1
在加载文件时读取文件的开头,而不是之后。 - 小聪聪到此一游
显示剩余11条评论
5个回答

0

只是为了好玩,这里使用了工作线程和文件系统访问 API

不知道这两个东西是否有帮助,我没有6GB的文件。 将读取操作放在主线程之外可以提高性能。

Object.assign(
    new Worker(
        URL.createObjectURL(
            new Blob(
                [
                    `self.onmessage = async (e) =>` +
                    `    void postMessage(` +
                    `        (new FileReaderSync())` +
                    `            .readAsText(` +
                    `                (await e.data.getFile())` +
                    `                    .slice(0,1024*10)` +
                    `            )` +
                    `    );`
                ],
                { type: 'application/javascript' }
            )
        )
    ),
    { onmessage: (e) => void console.log(e.data) }
).postMessage(
    (await window.showOpenFilePicker(
        { mode: 'read', startIn: 'documents' }
    )).pop()
);

编辑:

抱歉,但您现在需要使用Chromium(已在Edge上测试)。此外,这不会在jsfiddle中运行,因为Web Worker会导致安全问题。但是,您可以将其复制粘贴到Google控制台中运行。由于某种原因,标头并不能阻止程序的运行。如果这确实有所帮助,请将Worker放入自己的文件中(并删除我的艺术性负空间三角形)。


-1
使用流 API?
function (event) {
  const file = event.target.files[0];
  if (!file) {
    return;
  }
  const stream = file.slice(0, 1024 * 10).stream();
  const reader = stream.getReader();

  reader.read().then(function processText({ done, value }) {
    if (done) {
      console.log('Stream complete');
      return;
    }

    console.log(new TextDecoder('utf-8').decode(value));

    reader.read().then(processText);
  });

他们说他们尝试过了,但是没有改善任何事情。 - undefined
啊,抱歉,是我的错。 - undefined

-1
我在运行下面的代码时没有看到任何延迟。(dmg文件大小约为220MB)
你自己试试看。

enter image description here


-2
我认为在你的情况下,要获取文件浏览器的切片,仍然需要读取整个文件来创建一个从原始文件中切割出来的切片。
可能订阅"progress"事件可以帮助更快地访问文件内容,然后中止读取过程。
根据MDN的说法,Blob接口的slice()方法创建并返回一个包含来自调用它的Blob对象的子集数据的新Blob对象。
我们也可以从Chrome Blob类的README中得出相同的结论。

-3

这个怎么样!

function readFirstBytes(file, n) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      resolve(reader.result);
    };
    reader.onerror = reject;
    reader.readAsArrayBuffer(file.slice(0, n));
  });
}

readFirstBytes('file', 10).then(buffer => {
  console.log(buffer);
});

2
这如何提高前N个字节的读取性能? 你测试过多个文件大小吗?为什么这种方法会与我帖子中描述的方法有所不同? - kacase

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接