我正在构建一个应用程序,除此之外还可以将文件上传到现有API。此API在JSON对象中获取文件元数据和内容,因此我需要将文件的二进制内容转换为base64编码的字符串。
由于这是一个潜在的繁重操作,我将功能转移到了Web工作者。 worker使用二进制文件内容(从ArrayBuffer
返回)接收FileReader.readAsArrayBuffer()
对象,并返回base64编码的字符串。
这适用于较小的文件,但对于我需要支持的最大文件(~40 MB),这会导致我的工作程序的内存不足(Internet Explorer中的8007000E)。在极少数情况下它会通过,但大部分时间工人都会死。在将它移动到工作者之前发生了同样的事情,除了整个浏览器页面崩溃(在IE和Chrome中)。与IE相比,Chrome似乎对工作者的内存压力更具弹性,但我仍然需要在IE(10 +)中使其正常工作。
我的工人:
onmessage = e => {
const bytes = new Uint8Array(e.data);
const l = bytes.length;
const chars = new Array(l);
for (let i = 0, j = l - 1; i <= j; ++i, --j) {
chars[i] = String.fromCharCode(bytes[i]);
chars[j] = String.fromCharCode(bytes[j]);
}
const byteString = chars.join('');
const base64bytes = btoa(byteString);
try {
postMessage(base64bytes, [base64bytes]);
} catch (e) {
postMessage(base64bytes);
}
};
我在这里做了一些大的no-nos吗?有没有办法减少内存消耗?我想到的一个解决方案是以块为单位处理内容而不是整个文件,然后连接结果字符串并在外部对其进行编码。这会是可行的,还是会导致问题呢?还有其他我不知道的神奇功能吗?我对FileReader.readAsBinaryString()
有一线希望,但它现在已从标准中移除(无论如何都不支持IE10),所以我无法使用它。
(我意识到这个问题也可能与Code Review有关,但由于我的代码实际上崩溃了,我认为这是正确的地方)
答案 0 :(得分:0)
我想到的一个解决方案是处理块中的内容而不是整个文件,然后连接生成的字符串并在外部对其进行编码。这会是可行的,还是会导致问题呢?
这就是https://github.com/beatgammit/base64-js似乎做的事情,一次做〜16k。使用这个,不使用transferables(因为IE 10不支持它们)在我的计算机上,Chrome设法编码190mb ArrayBuffer(大于此抱怨无效的字符串长度),IE 11 40mb(大于此我得到一个内存不足的例外)。
您可以在https://plnkr.co/edit/SShi1PE4DuMATcyqTRPx?p=preview处看到此信息,其中工作人员拥有代码
var worker = new Worker('worker.js');
var length = 1024 * 1024 * 40;
worker.postMessage(new ArrayBuffer(length));
worker.onmessage = function(e) {
console.log('Received Base64 in UI thread', e.data.length, 'bytes');
}
和主线程
var worker = new Worker('worker.js');
var length = 1024 * 1024 * 60;
var buffer = new ArrayBuffer(length);
var maxMessageLength = 1024 * 1024;
var i = 0;
function next() {
var end = Math.min(i + maxMessageLength, length);
var copy = buffer.slice(i, end);
worker.postMessage(copy);
i = end;
}
var results = [];
worker.onmessage = function(e) {
results.push(e.data);
if (i < length) {
next();
} else {
results = results.join('');
alert('done ' + results.length);
}
};
next();
要超出40mb的限制,一种看似有希望的方法是,一次只将一个较小的切片传递给工人(比如1mb),对其进行编码,返回结果,然后才将下一个切片传递给工人,最后将所有结果连接起来。我设法使用它来编码更大的缓冲区(在IE 11中高达250mb)。我怀疑异步性允许垃圾收集器在调用之间运行。
例如at https://plnkr.co/edit/un7TXeHwYu8eBltfYAII?p=preview,在上面的worker中使用相同的代码,但是在UI线程中:
<i>
for(i=0;i<20000;i++)
{
//System.out.println(volts[i]);
if(i!=0)
g2d.draw(new Line2D.Double(time-(timeScale/y),-(((voltScale/x)*50*volts[i-1])-300),time,-(((voltScale/x)*50*volts[i])-300)));
time+=(timeScale/y);
}
</i>