Chrome、文件读取器API、event.target.result ===“”
Chrome, FileReader API, event.target.result === ""
我有一个网络应用程序,它通过 FileReader
API 的 readAsText()
方法对大文本文件(> 500mb)进行一些处理。
它多年来一直运行良好,但突然间我得到了空洞的回应:event.target.result
是一个空字符串。
369MB 有效,但 589MB 无效。
我在多台电脑上测试过;结果相同,但它在 Firefox 中确实有效。
Chrome 一定是在最近的更新中引入了这个。
这个bug提交了吗?
有什么解决方法吗?
这是 v8 对字符串长度的限制。
Has this bug been submitted?
这是负责的提交: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
Is there any workaround?
解决该问题的唯一方法是按块处理您的文本。
幸运的是,您正在处理 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()
方法,其中 returns 一个 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' );
}
} );
这是读取大文件的替代(现代)解决方案
file_or_blob
.stream()
.pipeThrough(new TextDecoderStream())
.pipeTo(new WritableStream({
write(textChunk) {
// document.body.append(textChunk)
console.log(textChunk)
}
}))
我有一个网络应用程序,它通过 FileReader
API 的 readAsText()
方法对大文本文件(> 500mb)进行一些处理。
它多年来一直运行良好,但突然间我得到了空洞的回应:event.target.result
是一个空字符串。
369MB 有效,但 589MB 无效。
我在多台电脑上测试过;结果相同,但它在 Firefox 中确实有效。 Chrome 一定是在最近的更新中引入了这个。
这个bug提交了吗?
有什么解决方法吗?
这是 v8 对字符串长度的限制。
Has this bug been submitted?
这是负责的提交: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
Is there any workaround?
解决该问题的唯一方法是按块处理您的文本。
幸运的是,您正在处理 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()
方法,其中 returns 一个 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' );
}
} );
这是读取大文件的替代(现代)解决方案
file_or_blob
.stream()
.pipeThrough(new TextDecoderStream())
.pipeTo(new WritableStream({
write(textChunk) {
// document.body.append(textChunk)
console.log(textChunk)
}
}))