【问题标题】:Chrome, FileReader API, event.target.result === ""Chrome, FileReader API, event.target.result === ""
【发布时间】:2020-07-30 22:53:54
【问题描述】:

我有一个网络应用程序,它通过 FileReader API 的 readAsText() 方法对大文本文件 (> 500mb) 进行一些处理。
它多年来一直很好用,但突然间我得到了空的回复:event.target.result 是一个空字符串。

369MB 有效,但 589MB 无效。

我在多台电脑上测试过;结果相同,但它在 Firefox 中确实有效。 Chrome 一定是在最近的更新中引入了这一点。

这个bug提交了吗?

有什么解决办法吗?

【问题讨论】:

  • 您意识到生成的字符串(使用 UCS-2 编码在内部存储)将超过 1GB 的内存?您需要开发一种流式处理方法,以便以较小的块处理此文件。
  • 正如我所说的。直到最近,这在 Chrome 中一直运行良好。在 Firefox 中工作。
  • 仅仅因为它有效并不能使它成为一种可行的方法。期望任何浏览器在缓冲 1GB 字符串时不会耗尽进程内存是不合理的。
  • 您在reader.onerror 属性中遇到任何错误?
  • 您的文件来自哪里?你的文本文件使用什么编码? (Ps:我最近也遇到了这个问题......甚至发生在 XHR 的网络请求中)

标签: javascript google-chrome filereader


【解决方案1】:

这是读取大文件的替代(现代)解决方案

file_or_blob
  .stream()
  .pipeThrough(new TextDecoderStream())
  .pipeTo(new WritableStream({
    write(textChunk) {
      // document.body.append(textChunk)
      console.log(textChunk)
    }
  }))

【讨论】:

  • 我刚刚发现,通过分块读取大文件,该解决方案可以完美运行。但是,如果我将所有块存储在一个变量中(也就是说合并所有块),它仍然会导致问题吗?换句话说,这个新变量将再次大于 500 MB @Endless
  • 是的。最后它和var text = await blob.text() 是一样的,如果你愿意,你可以将它悬停保存为字符串数组var body = ["hello wo", "rld"]
  • 感谢您的回答!所以你是说基本上我可以将这些块存储在字符串数组中?由于我最终需要将其作为一个字符串发送到后端,因此我可以在发送之前将它们合并。会好吗? @无尽
  • 我的建议是改为上传 blob。可以使用 FormData。如果您不需要读取文件,那就更好了
【解决方案2】:

这是 v8 对字符串长度的限制。

这个bug提交了吗?

这是负责的提交:https://github.com/v8/v8/commit/ea56bf5513d0cbd2a35a9035c5c2996272b8b728

this Change-Log 上运行一个二分法,发现它已应用于 Chrome v79。

在此更改之前,64 位平台的限制设置为 1024MB,新限制为 512MB,即一半。

这意味着不仅 FileReader 受到影响,任何试图产生如此大字符串的方法也受到影响。

这是一个简单的例子:

const header = 24;
const bytes = new Uint8Array( (512 * 1024 * 1024) - header );
let txt = new TextDecoder().decode( bytes );
console.log( txt.length ); // 536870888
txt += "f"; // RangeError

有什么解决办法吗?

解决该问题的唯一方法是按块处理您的文本。

幸运的是,您正在处理 ASCII 数据,因此您可以使用 Blob.slice() 方法轻松拆分资源并处理该块:

// working in a Web-Worker to not freeze the tab while generating the data
const worker_script = `
(async () => {

  postMessage( 'Generating file, may take some time...' );

  const bytes = Uint8Array.from(
    { length: 800 * 1024 * 1024 },
    (_, i) => (i % 25) + 65
  );
  const blob = new Blob( [ bytes ] );

  const length = blob.size;
  const chunk_size = 128 * 1024 * 1024;

  postMessage( 'Original file size: ' + length );
  
  let As = 0;
  let i = 0;
  while ( i < length ) {
    const str = await blob.slice( i, i + chunk_size ).text();
    i += chunk_size;
    As += str.split( 'A' ).length - 1;
  }
  postMessage( 'found ' + As + ' "A"s in the whole file' );

} )();
`;
const worker_blob = new Blob( [ worker_script ] );
const worker = new Worker( URL.createObjectURL( worker_blob ) );
worker.onmessage = (evt) => console.log( evt.data );

处理像 UTF-8 这样的富文本必须处理多字节字符,而这可能不是那么容易...

还要注意,即使在允许您生成如此大字符串的浏览器中,您也可能会遇到其他问题。例如在 Safari 中,您可以生成更大的字符串,但如果您将其在内存中保存的时间过长,那么浏览器将自动重新加载您的页面。


2021 年更新

现在几乎所有现代浏览器都支持Blob.stream() 方法,该方法返回一个ReadableStream,让我们能够很好地...以流的形式读取该Blob 的内容。因此,我们可以以更高效的方式处理大型文件文本,并且由于 TextDecoder API 的流选项,我们甚至可以处理非 ASCII 字符:

const bytes = Uint8Array.from(
  { length: 800 * 1024 * 1024 },
  (_, i) => (i % 25) + 65
);
const blob = new Blob( [ bytes ] );

console.log( 'Original file size: ' + blob.size );
const reader = blob.stream().getReader();
const decoder = new TextDecoder();
let As = 0;
reader.read().then( function process({ done, value }) {
  const str = decoder.decode( value, { stream: true } );
  As += str.split( 'A' ).length - 1;
  if( !done ) {
    reader.read().then( process );
  }
  else {
    console.log( 'found ' + As + ' "A"s in the whole file' );
  }
} );

【讨论】:

  • 很好的答案。那是提交修复吗?那么前一个允许 230,bug 版本 228 和修复 229? 229 是多少兆字节?我可以在某个地方看到何时部署此修复程序吗?
  • 2**29512*1024*1024 -> 512MB,这是当前的限制...在我的回答中这些提交有些奇怪... ChangeLog 来自 7 个月前,而 v8提交是从 2 个月前开始的。我想我在 ChangeLog 中发现了 1024MB -> 256MB 的变化,但不知何故错过了 256 -> 512 的凹凸...我将运行一个新的平分法
猜你喜欢
  • 1970-01-01
  • 2011-09-28
  • 1970-01-01
  • 2017-05-02
  • 1970-01-01
  • 2016-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多