在将文件写入客户端磁盘时从 blob 创建/对象 URL 释放内存
Releasing memory from blob creation / object URL in writing file to client's disk
更新
自从问了下面的问题并在发现代码中的错误后得出了一个更基本的问题后,我找到了更多信息,例如在 MDN 网络文档中找到了下载 API 方法 downloads.download() 它声明对象 url 的撤销只应在 下载 file/url 之后执行。因此,我花了一些时间试图了解 Web 扩展是否会下载网页的 API onChanged 事件 'available' 到 javascript 并且我认为不会。我不明白为什么下载 API 仅适用于扩展程序,尤其是当有很多关于同一 memory-usage/object-url-revocation 问题的问题时。例如,Wait for user to finish downloading a blob in Javascript.
如果你知道,请解释一下好吗?谢谢。
从关闭 Firefox 浏览器开始,然后右键单击要在 Firefox 中打开的本地 html 文件,它会在 Windows 任务管理器中显示五个 firefox.exe 进程。其中四个进程开始时的内存在 20,000k 到 25,000k 之间,一个进程大约有 115,000k。
此 html 页面有一个 indexedDB 数据库,其中包含 50 个对象存储,每个包含 50 个对象。每个对象都从其对象存储中提取并使用 JSON.stringify 转换为字符串,然后写入二维数组。之后,数组的所有元素被连接成一个大字符串,转换为一个 blob 并通过一个 URL 对象写入硬盘,该对象随后立即被撤销。最终文件约190MB。
如果代码在转换为 blob 之前停止,其中一个 firefox.exe 进程的内存使用量会增加到大约 425,000k,然后在元素之后的大约 5-10 秒内回落到 25,000k该数组已连接成一个字符串。
如果代码 运行 完成,同一 firefox.exe 进程的内存使用量会增长到大约 1,000,000k,然后下降到大约 225,000k。从 115,000k 开始的 firefox.exe 进程也在代码的 blob 阶段增加到大约 325,000k 并且永远不会减少。
将 blob 作为文本文件写入磁盘后,这两个 firefox.exe 进程永远不会释放大约 2 x 200,000k 的内存增量。
我已将每个函数中使用的每个变量都设置为空,除非刷新页面,否则永远不会释放内存。此外,此过程由按钮单击事件启动;如果在没有中间刷新的情况下再次 运行,则这两个 firefox.exe 进程中的每一个都会额外获取 200,000k 的内存,每个 运行.
我还没弄清楚如何释放内存?
这两个函数很简单。 json[i][j] 保存数据库中第 i 个对象存储中第 j 个对象的字符串版本。 os_data[] 是一个小对象数组 { "name" : objectStoreName, "count" : n },其中 n 是存储中对象的数量。如果未调用 write_to_disk,build_text 函数似乎会释放内存。因此,问题似乎与 blob 或 url.
有关
我可能忽略了一些显而易见的事情。感谢您提供任何指导。
编辑:
我从 JavaScript: Create and save file 看到我在 revokeObjectURL(blob) 语句中有一个错误。它不能撤销 blob,createObjectURL(blob) 需要保存到像 url 这样的变量中,然后撤销 url,而不是 blob。
这在大多数情况下都有效,并且在大多数情况下,内存会从上述两个 firefox.exe 进程中释放。这给我留下了一个关于 url.
撤销时间的小问题
如果撤销允许释放内存,那么 url 是否应该仅在文件下载成功后才被撤销?如果撤销发生在用户单击确定下载文件之前,会发生什么情况?假设我单击按钮从数据库中准备文件,准备就绪后浏览器会弹出 window 进行下载,但我稍等片刻,想着文件的名称或保存位置,不会'撤销声明已经 运行 但浏览器仍 url 是 'held' 因为它将被下载?我知道我仍然可以下载文件,但是撤销是否仍然释放内存?从我对这个例子的少量试验来看,它似乎没有在这种情况下发布。
如果有一个事件在文件成功或不成功下载到客户端时触发,是不是应该撤销 url 的时间?在撤销 url 之前设置几分钟超时会更好吗,因为我很确定没有指示客户端下载已结束的事件。
我可能不了解这方面的一些基本知识。谢谢
function build_text() {
var i, j, l, txt = "";
for ( i = 1; i <=50; i++ ) {
l = os_data[i-1].count;
for ( j = 1; j <= l; j++ ) {
txt += json[i][j] + '\n';
}; // next j
}; // next i
write_to_disk('indexedDB portfolio', txt);
txt = json = null;
} // close build_text
function write_to_disk( fileName, data ) {
fileName = fileName.replace(".","");
var blob = new Blob( [data], { type: 'text/csv' } ), elem;
if ( window.navigator.msSaveOrOpenBlob ) {
window.navigator.msSaveBlob(blob, fileName);
} else {
elem = window.document.createElement('a');
elem.href = window.URL.createObjectURL(blob);
elem.download = fileName;
document.body.appendChild(elem);
elem.click();
document.body.removeChild(elem);
window.URL.revokeObjectURL(blob);
}; // end if
data = blob = elem = fileName = null;
} // close write_to_disk
我对这里的问题有点迷惑...
但让我们试着回答,至少回答一部分:
首先让我们解释一下 URL.createObjectURL(blob)
的大致作用:
它创建一个 blob URI,它是一个指向内存中 Blob blob
的 URI,就好像它在一个可到达的地方(比如服务器)。
这个 blob URI 将标记 blob
为垃圾收集器 (GC) 无法收集的,只要它没有被撤销,这样您就不必在您的脚本中保持对 blob
的实时引用,但您仍然可以 use/load 它。
URL.revokeObjectURL
将打破 blob URI 和内存中 Blob 之间的 link。它不会直接释放blob
所占用的内存,它只会取消自己对GC的保护,[并且不会再指向任何地方]。
因此,如果您有多个指向同一个 Blob 对象的 blob URI,则仅撤销一个不会破坏其他 blob URI。
现在,只有在 GC 启动时才会释放内存,这仅由浏览器内部决定,当它认为是最佳时间时,或者当它看到它没有其他选择时(通常当它错过 memroy space).
所以你没有看到你的内存被立即释放是很正常的,根据经验,我会说 FF 不关心使用大量内存,当它可用时,让 GC不要经常踢,whihc 有利于用户体验(GCing 经常导致滞后)。
对于您的下载问题,事实上,Web API 不提供一种方法来判断下载是成功还是失败,即使下载刚刚结束也不提供。
至于撤销部分,这真的取决于你什么时候做。
如果直接在click handler中做,那么浏览器还没有做pre-fetch request,所以当点击的默认动作(下载)发生时,不会有任何东西link 不再由 URI 编辑。
现在,如果您在 "save" 提示后撤销 blob URI,浏览器将完成预取请求,因此可能能够自行标记不应清除 Blob 资源。但我认为这种行为不受任何规范的约束,最好至少等待 window 的 focus
事件,此时资源的下载应该已经有了开始了。
const blob = new Blob(['bar']);
const uri = URL.createObjectURL(blob);
anchor.href = uri;
anchor.onclick = e => {
window.addEventListener('focus', e=>{
URL.revokeObjectURL(uri);
console.log("Blob URI revoked, you won't be able to download it anymore");
}, {once: true});
};
<a id="anchor" download="foo.txt">download</a>
更新
自从问了下面的问题并在发现代码中的错误后得出了一个更基本的问题后,我找到了更多信息,例如在 MDN 网络文档中找到了下载 API 方法 downloads.download() 它声明对象 url 的撤销只应在 下载 file/url 之后执行。因此,我花了一些时间试图了解 Web 扩展是否会下载网页的 API onChanged 事件 'available' 到 javascript 并且我认为不会。我不明白为什么下载 API 仅适用于扩展程序,尤其是当有很多关于同一 memory-usage/object-url-revocation 问题的问题时。例如,Wait for user to finish downloading a blob in Javascript.
如果你知道,请解释一下好吗?谢谢。
从关闭 Firefox 浏览器开始,然后右键单击要在 Firefox 中打开的本地 html 文件,它会在 Windows 任务管理器中显示五个 firefox.exe 进程。其中四个进程开始时的内存在 20,000k 到 25,000k 之间,一个进程大约有 115,000k。
此 html 页面有一个 indexedDB 数据库,其中包含 50 个对象存储,每个包含 50 个对象。每个对象都从其对象存储中提取并使用 JSON.stringify 转换为字符串,然后写入二维数组。之后,数组的所有元素被连接成一个大字符串,转换为一个 blob 并通过一个 URL 对象写入硬盘,该对象随后立即被撤销。最终文件约190MB。
如果代码在转换为 blob 之前停止,其中一个 firefox.exe 进程的内存使用量会增加到大约 425,000k,然后在元素之后的大约 5-10 秒内回落到 25,000k该数组已连接成一个字符串。
如果代码 运行 完成,同一 firefox.exe 进程的内存使用量会增长到大约 1,000,000k,然后下降到大约 225,000k。从 115,000k 开始的 firefox.exe 进程也在代码的 blob 阶段增加到大约 325,000k 并且永远不会减少。
将 blob 作为文本文件写入磁盘后,这两个 firefox.exe 进程永远不会释放大约 2 x 200,000k 的内存增量。
我已将每个函数中使用的每个变量都设置为空,除非刷新页面,否则永远不会释放内存。此外,此过程由按钮单击事件启动;如果在没有中间刷新的情况下再次 运行,则这两个 firefox.exe 进程中的每一个都会额外获取 200,000k 的内存,每个 运行.
我还没弄清楚如何释放内存?
这两个函数很简单。 json[i][j] 保存数据库中第 i 个对象存储中第 j 个对象的字符串版本。 os_data[] 是一个小对象数组 { "name" : objectStoreName, "count" : n },其中 n 是存储中对象的数量。如果未调用 write_to_disk,build_text 函数似乎会释放内存。因此,问题似乎与 blob 或 url.
有关我可能忽略了一些显而易见的事情。感谢您提供任何指导。
编辑:
我从 JavaScript: Create and save file 看到我在 revokeObjectURL(blob) 语句中有一个错误。它不能撤销 blob,createObjectURL(blob) 需要保存到像 url 这样的变量中,然后撤销 url,而不是 blob。
这在大多数情况下都有效,并且在大多数情况下,内存会从上述两个 firefox.exe 进程中释放。这给我留下了一个关于 url.
撤销时间的小问题如果撤销允许释放内存,那么 url 是否应该仅在文件下载成功后才被撤销?如果撤销发生在用户单击确定下载文件之前,会发生什么情况?假设我单击按钮从数据库中准备文件,准备就绪后浏览器会弹出 window 进行下载,但我稍等片刻,想着文件的名称或保存位置,不会'撤销声明已经 运行 但浏览器仍 url 是 'held' 因为它将被下载?我知道我仍然可以下载文件,但是撤销是否仍然释放内存?从我对这个例子的少量试验来看,它似乎没有在这种情况下发布。
如果有一个事件在文件成功或不成功下载到客户端时触发,是不是应该撤销 url 的时间?在撤销 url 之前设置几分钟超时会更好吗,因为我很确定没有指示客户端下载已结束的事件。
我可能不了解这方面的一些基本知识。谢谢
function build_text() {
var i, j, l, txt = "";
for ( i = 1; i <=50; i++ ) {
l = os_data[i-1].count;
for ( j = 1; j <= l; j++ ) {
txt += json[i][j] + '\n';
}; // next j
}; // next i
write_to_disk('indexedDB portfolio', txt);
txt = json = null;
} // close build_text
function write_to_disk( fileName, data ) {
fileName = fileName.replace(".","");
var blob = new Blob( [data], { type: 'text/csv' } ), elem;
if ( window.navigator.msSaveOrOpenBlob ) {
window.navigator.msSaveBlob(blob, fileName);
} else {
elem = window.document.createElement('a');
elem.href = window.URL.createObjectURL(blob);
elem.download = fileName;
document.body.appendChild(elem);
elem.click();
document.body.removeChild(elem);
window.URL.revokeObjectURL(blob);
}; // end if
data = blob = elem = fileName = null;
} // close write_to_disk
我对这里的问题有点迷惑...
但让我们试着回答,至少回答一部分:
首先让我们解释一下 URL.createObjectURL(blob)
的大致作用:
它创建一个 blob URI,它是一个指向内存中 Blob blob
的 URI,就好像它在一个可到达的地方(比如服务器)。
这个 blob URI 将标记 blob
为垃圾收集器 (GC) 无法收集的,只要它没有被撤销,这样您就不必在您的脚本中保持对 blob
的实时引用,但您仍然可以 use/load 它。
URL.revokeObjectURL
将打破 blob URI 和内存中 Blob 之间的 link。它不会直接释放blob
所占用的内存,它只会取消自己对GC的保护,[并且不会再指向任何地方]。
因此,如果您有多个指向同一个 Blob 对象的 blob URI,则仅撤销一个不会破坏其他 blob URI。
现在,只有在 GC 启动时才会释放内存,这仅由浏览器内部决定,当它认为是最佳时间时,或者当它看到它没有其他选择时(通常当它错过 memroy space).
所以你没有看到你的内存被立即释放是很正常的,根据经验,我会说 FF 不关心使用大量内存,当它可用时,让 GC不要经常踢,whihc 有利于用户体验(GCing 经常导致滞后)。
对于您的下载问题,事实上,Web API 不提供一种方法来判断下载是成功还是失败,即使下载刚刚结束也不提供。
至于撤销部分,这真的取决于你什么时候做。
如果直接在click handler中做,那么浏览器还没有做pre-fetch request,所以当点击的默认动作(下载)发生时,不会有任何东西link 不再由 URI 编辑。
现在,如果您在 "save" 提示后撤销 blob URI,浏览器将完成预取请求,因此可能能够自行标记不应清除 Blob 资源。但我认为这种行为不受任何规范的约束,最好至少等待 window 的 focus
事件,此时资源的下载应该已经有了开始了。
const blob = new Blob(['bar']);
const uri = URL.createObjectURL(blob);
anchor.href = uri;
anchor.onclick = e => {
window.addEventListener('focus', e=>{
URL.revokeObjectURL(uri);
console.log("Blob URI revoked, you won't be able to download it anymore");
}, {once: true});
};
<a id="anchor" download="foo.txt">download</a>