Wil*_*llH 11 javascript html5 google-chrome azure fileapi
我有一个需要将大文件上传到Azure BLOB存储的Web应用程序.我的解决方案使用HTML5 File API切片成块,然后将块作为blob块放置,块的ID存储在数组中,然后块作为blob提交.
该解决方案在IE中运行良好.在64位Chrome上,我已经成功上传了4Gb文件但是看到了非常大的内存使用量(2Gb +).在32位Chrome上,特定的镀铬工艺将达到约500-550Mb然后崩溃.
我看不到任何明显的内存泄漏或我可以改变以帮助垃圾收集的事情.我将块ID存储在一个数组中,所以显然会有一些内存蠕变,但这不应该很大.这几乎就像File API将整个文件保存在内存中一样.
它是作为一个从控制器调用的Angular服务编写的,我认为只是服务代码是相关的:
(function() {
'use strict';
angular
.module('app.core')
.factory('blobUploadService',
[
'$http', 'stringUtilities',
blobUploadService
]);
function blobUploadService($http, stringUtilities) {
var defaultBlockSize = 1024 * 1024; // Default to 1024KB
var stopWatch = {};
var state = {};
var initializeState = function(config) {
var blockSize = defaultBlockSize;
if (config.blockSize) blockSize = config.blockSize;
var maxBlockSize = blockSize;
var numberOfBlocks = 1;
var file = config.file;
var fileSize = file.size;
if (fileSize < blockSize) {
maxBlockSize = fileSize;
}
if (fileSize % maxBlockSize === 0) {
numberOfBlocks = fileSize / maxBlockSize;
} else {
numberOfBlocks = parseInt(fileSize / maxBlockSize, 10) + 1;
}
return {
maxBlockSize: maxBlockSize,
numberOfBlocks: numberOfBlocks,
totalBytesRemaining: fileSize,
currentFilePointer: 0,
blockIds: new Array(),
blockIdPrefix: 'block-',
bytesUploaded: 0,
submitUri: null,
file: file,
baseUrl: config.baseUrl,
sasToken: config.sasToken,
fileUrl: config.baseUrl + config.sasToken,
progress: config.progress,
complete: config.complete,
error: config.error,
cancelled: false
};
};
/* config: {
baseUrl: // baseUrl for blob file uri (i.e. http://<accountName>.blob.core.windows.net/<container>/<blobname>),
sasToken: // Shared access signature querystring key/value prefixed with ?,
file: // File object using the HTML5 File API,
progress: // progress callback function,
complete: // complete callback function,
error: // error callback function,
blockSize: // Use this to override the defaultBlockSize
} */
var upload = function(config) {
state = initializeState(config);
var reader = new FileReader();
reader.onloadend = function(evt) {
if (evt.target.readyState === FileReader.DONE && !state.cancelled) { // DONE === 2
var uri = state.fileUrl + '&comp=block&blockid=' + state.blockIds[state.blockIds.length - 1];
var requestData = new Uint8Array(evt.target.result);
$http.put(uri,
requestData,
{
headers: {
'x-ms-blob-type': 'BlockBlob',
'Content-Type': state.file.type
},
transformRequest: []
})
.success(function(data, status, headers, config) {
state.bytesUploaded += requestData.length;
var percentComplete = ((parseFloat(state.bytesUploaded) / parseFloat(state.file.size)) * 100
).toFixed(2);
if (state.progress) state.progress(percentComplete, data, status, headers, config);
uploadFileInBlocks(reader, state);
})
.error(function(data, status, headers, config) {
if (state.error) state.error(data, status, headers, config);
});
}
};
uploadFileInBlocks(reader, state);
return {
cancel: function() {
state.cancelled = true;
}
};
};
function cancel() {
stopWatch = {};
state.cancelled = true;
return true;
}
function startStopWatch(handle) {
if (stopWatch[handle] === undefined) {
stopWatch[handle] = {};
stopWatch[handle].start = Date.now();
}
}
function stopStopWatch(handle) {
stopWatch[handle].stop = Date.now();
var duration = stopWatch[handle].stop - stopWatch[handle].start;
delete stopWatch[handle];
return duration;
}
var commitBlockList = function(state) {
var uri = state.fileUrl + '&comp=blocklist';
var requestBody = '<?xml version="1.0" encoding="utf-8"?><BlockList>';
for (var i = 0; i < state.blockIds.length; i++) {
requestBody += '<Latest>' + state.blockIds[i] + '</Latest>';
}
requestBody += '</BlockList>';
$http.put(uri,
requestBody,
{
headers: {
'x-ms-blob-content-type': state.file.type
}
})
.success(function(data, status, headers, config) {
if (state.complete) state.complete(data, status, headers, config);
})
.error(function(data, status, headers, config) {
if (state.error) state.error(data, status, headers, config);
// called asynchronously if an error occurs
// or server returns response with an error status.
});
};
var uploadFileInBlocks = function(reader, state) {
if (!state.cancelled) {
if (state.totalBytesRemaining > 0) {
var fileContent = state.file.slice(state.currentFilePointer,
state.currentFilePointer + state.maxBlockSize);
var blockId = state.blockIdPrefix + stringUtilities.pad(state.blockIds.length, 6);
state.blockIds.push(btoa(blockId));
reader.readAsArrayBuffer(fileContent);
state.currentFilePointer += state.maxBlockSize;
state.totalBytesRemaining -= state.maxBlockSize;
if (state.totalBytesRemaining < state.maxBlockSize) {
state.maxBlockSize = state.totalBytesRemaining;
}
} else {
commitBlockList(state);
}
}
};
return {
upload: upload,
cancel: cancel,
startStopWatch: startStopWatch,
stopStopWatch: stopStopWatch
};
};
})();
Run Code Online (Sandbox Code Playgroud)
我有什么方法可以移动对象的范围来帮助Chrome GC吗?我见过其他人提到类似的问题,但了解Chromium已经解决了一些问题.
我应该说我的解决方案很大程度上基于Gaurav Mantri的博客文章:
\n\n\n我看不到任何明显的内存泄漏或可以更改以帮助垃圾收集的内容。我将块 ID 存储在一个数组中,因此显然\n 会有一些内存蠕变,但这不应该是巨大的。这几乎就像文件 API 将整个文件保存到内存中一样。
\n
你是对的。Blob创建的新s.slice()被保存在内存中。
解决方案是在处理或对象完成时Blob.prototype.close()调用引用。BlobBlobFile
另请注意,at at Question 还会创建if函数被多次调用的javascript新实例。FileReaderupload
\n \n\n\n\n该
\nslice()方法返回一个新Blob对象,其字节范围为\n 从可选start参数到\n 但不包括\n 可选end参数,并且type属性为\n 可选contentType参数的值。
Blob实例在 的生命周期中都存在document。虽然Blob一旦从其中删除就应该被垃圾收集Blob URL Store
\n \n\n\n\n\n\n注意:用户代理可以自由地垃圾收集从
\nBlob URL Store.
\n\n\n\n\n每个都
\nBlob必须有一个内部快照状态,该状态必须最初设置为底层存储的状态(如果存在任何此类底层存储),并且必须通过底层存储进行保留StructuredClone。可以为 s 找到快照状态的进一步规范定义File。
\n \n\n\n\n该
\n\nclose()方法据说是close为了Blob,并且必须按如下方式运行:\n
\n- 如果
\nreadability state上下文对象的CLOSED,则终止该算法。- 否则,将 的
\nreadability state设置context object为CLOSED。- 如果上下文对象在 中有条目
\nBlob URL Store,则删除对应于 的条目context object。
如果将Blob对象传递给URL.createObjectURL(),则调用URL.revokeObjectURL()或Blob对象File,然后调用.close()。
\n \n\n\n\n\n\n通过从 Blob URL 存储中删除相应的条目来撤销
\nBlob URL字符串中提供的内容。url此方法必须按如下方式执行\n:\n 1. 如果 引用url具有Blobareadability state的aCLOSED,或者如果为参数提供的值url不是 aBlob URL,或者如果为参数提供的值url不具有 a中的条目Blob URL Store,此方法调用不执行任何操作。用户代理可能会在错误控制台上显示一条消息。\n 2. 否则,用户代理必须remove the entry从Blob URL Storeforurl.
您可以通过打开查看这些调用的结果
\n\nchrome://blob-internals \nRun Code Online (Sandbox Code Playgroud)\n\nBlob查看创建和关闭呼叫之前和之后的详细信息Blob。
例如,从
\n\nxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\nRefcount: 1\nContent Type: text/plain\nType: data\nLength: 3\nRun Code Online (Sandbox Code Playgroud)\n\n到
\n\nxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\nRefcount: 1\nContent Type: text/plain\nRun Code Online (Sandbox Code Playgroud)\n\n以下致电.close(). 同样来自
blob:http://example.com/c2823f75-de26-46f9-a4e5-95f57b8230bd\nUuid: 29e430a6-f093-40c2-bc70-2b6838a713bc\nRun Code Online (Sandbox Code Playgroud)\n\n另一种方法是将文件作为ArrayBuffer数组缓冲区或数组缓冲区块发送。然后在服务器上重新组装文件。
或者您可以调用每个FileReader构造函数、FileReader.prototype.readAsArrayBuffer()和load事件FileReader一次。
在传递toload的情况下,使用, , ,来获取作为at from的块。当处理完等于 的块时,将 s 数组传递给构造函数,以在浏览器中将文件部分重新组合成单个文件;然后发送 到服务器。FileReaderArrayBufferUint8ArrayReadableStreamTypedArray.prototype.subarray().getReader().read()NArrayBufferTypedArraypullUint8ArrayN.byteLengthArrayBufferUint8ArrayBlobBlob
<!DOCTYPE html>\n<html>\n\n<head>\n</head>\n\n<body>\n <input id="file" type="file">\n <br>\n <progress value="0"></progress>\n <br>\n <output for="file"><img alt="preview"></output>\n <script type="text/javascript">\n const [input, output, img, progress, fr, handleError, CHUNK] = [\n document.querySelector("input[type=\'file\']")\n , document.querySelector("output[for=\'file\']")\n , document.querySelector("output img")\n , document.querySelector("progress")\n , new FileReader\n , (err) => console.log(err)\n , 1024 * 1024\n ];\n\n progress.addEventListener("progress", e => {\n progress.value = e.detail.value;\n e.detail.promise();\n });\n\n let [chunks, NEXT, CURR, url, blob] = [Array(), 0, 0];\n\n input.onchange = () => {\n NEXT = CURR = progress.value = progress.max = chunks.length = 0;\n if (url) {\n URL.revokeObjectURL(url);\n if (blob.hasOwnProperty("close")) {\n blob.close();\n }\n }\n\n if (input.files.length) {\n console.log(input.files[0]);\n progress.max = input.files[0].size;\n progress.step = progress.max / CHUNK;\n fr.readAsArrayBuffer(input.files[0]);\n }\n\n }\n\n fr.onload = () => {\n const VIEW = new Uint8Array(fr.result);\n const LEN = VIEW.byteLength;\n const {type, name:filename} = input.files[0];\n const stream = new ReadableStream({\n pull(controller) {\n if (NEXT < LEN) {\n controller\n .enqueue(VIEW.subarray(NEXT, !NEXT ? CHUNK : CHUNK + NEXT));\n NEXT += CHUNK;\n } else {\n controller.close();\n }\n },\n cancel(reason) {\n console.log(reason);\n throw new Error(reason);\n }\n });\n\n const [reader, processData] = [\n stream.getReader()\n , ({value, done}) => {\n if (done) {\n return reader.closed.then(() => chunks);\n }\n chunks.push(value);\n return new Promise(resolve => {\n progress.dispatchEvent(\n new CustomEvent("progress", {\n detail:{\n value:CURR += value.byteLength,\n promise:resolve\n }\n })\n ); \n })\n .then(() => reader.read().then(data => processData(data)))\n .catch(e => reader.cancel(e))\n }\n ];\n\n reader.read()\n .then(data => processData(data))\n .then(data => {\n blob = new Blob(data, {type});\n console.log("complete", data, blob);\n if (/image/.test(type)) {\n url = URL.createObjectURL(blob);\n img.onload = () => {\n img.title = filename;\n input.value = "";\n }\n img.src = url;\n } else {\n input.value = "";\n } \n })\n .catch(e => handleError(e))\n\n }\n </script>\n\n</body>\n\n</html>\nRun Code Online (Sandbox Code Playgroud)\n\nplnkr http://plnkr.co/edit/AEZ7iQce4QaJOKut71jk?p=preview
\n\n您还可以使用利用fetch()
fetch(new Request("/path/to/server/", {method:"PUT", body:blob}))\nRun Code Online (Sandbox Code Playgroud)\n\n\n \n\n\n\n\n
\n- 令body为 request\xe2\x80\x99s body。
\n- \n
如果body为 null,则根据请求对获取任务进行排队,以处理请求的请求主体末尾并中止这些步骤。
- \n
令read为从body\xe2\x80\x99s流中读取块的结果。
\n\n\n
- \n
当使用属性为 false 且属性是对象的对象完成读取时,运行以下子步骤:
\n\ndonevalueUint8Array\n
- 令bytes为对象表示的字节序列
\nUint8Array。- \n
传输字节。
- \n
将body\xe2\x80\x99s传输的字节数增加bytes\xe2\x80\x99s长度。
- \n
再次运行上述步骤。
- \n
当使用属性为 true 的对象完成读取时,根据请求
done对获取任务进行排队,以处理request的请求正文\n 。- \n
当读取满足与上述模式都不匹配的值时,或者读取被拒绝时,终止正在进行的读取,原因为fatal。
也可以看看
\n\n\n| 归档时间: |
|
| 查看次数: |
1030 次 |
| 最近记录: |