我是否已达到浏览器中 JavaScript 可以处理的对象大小的限制?

IT技术 javascript arrays memory
2021-01-29 07:45:27

<script>在 HTML 的标签中嵌入了一个大数组,就像这样(没什么奇怪的):

<script>
    var largeArray = [/* lots of stuff in here */];
</script>

在此特定示例中,数组有 210,000 个元素。这远低于理论最大值 2 31 - 4 个数量级这是有趣的部分:如果我将数组的 JS 源代码保存到一个文件中,那么该文件将大于 44 兆字节(准确地说是 46,573,399 字节)。

如果你想自己看看,你可以从 GitHub 下载(那里的所有数据都是罐头的,其中有很多是重复的。在生产中不会出现这种情况。)

现在,我真的不关心提供那么多数据。我的服务器对其响应进行 gzip 压缩,因此通过网络获取数据确实不需要那么长时间。但是,页面一旦加载就会使浏览器崩溃,这是一种非常令人讨厌的趋势我根本没有在 IE 中进行测试(这是一个内部工具)。我的主要目标是 Chrome 8 和 Firefox 3.6。

在 Firefox 中,我可以在控制台中看到一个相当有用的错误:

Error: script stack space quota is exhausted

在 Chrome 中,我只是得到了 sad-tab 页面:

在此处输入图片说明

切入正题,已经

  • 这是真正为我们的现代,“高性能”的浏览器来处理太多的数据?
  • 我能做些什么*来优雅地处理这么多数据?

顺便说一句,我能够让它在 Chrome 中时断时续地工作(阅读:不会使选项卡崩溃)。我真的认为至少 Chrome 是由更坚固的东西制成的,但显然我错了......


编辑 1

@Crayon:我不想证明为什么我想一次将这么多数据转储到浏览器中。简短版本:要么我解决这个(不可否认不是那么容易)问题,要么我必须解决一大堆其他问题。我现在选择更简单的方法。

@various:现在,我并不是特别在寻找实际减少数组中元素数量的方法。我知道我可以实现 Ajax 分页或 what-have-you,但这在其他方面给我带来了一系列问题。

@Phrogz:每个元素看起来像这样:

{dateTime:new Date(1296176400000),
 terminalId:'terminal999',
 'General___BuildVersion':'10.05a_V110119_Beta',
 'SSM___ExtId':26680,
 'MD_CDMA_NETLOADER_NO_BCAST___Valid':'false',
 'MD_CDMA_NETLOADER_NO_BCAST___PngAttempt':0}

@Will:但我有一台带有 4 核处理器、6 GB 内存、超过半 TB 磁盘空间的计算机......我什至没有要求浏览器快速执行此操作 - 我只是在问让它工作


编辑 2

任务完成!

JuanGuffa的现场建议,我能够让它发挥作用!看起来问题只是在解析源代码,而不是在内存中实际使用它。

总结一下 Juan 回答的评论泥潭:我不得不将我的大阵列拆分为一系列较小的阵列,然后再拆分Array#concat()它们,但这还不够。必须将它们放入单独的var语句中。像这样:

var arr0 = [...];
var arr1 = [...];
var arr2 = [...];
/* ... */
var bigArray = arr0.concat(arr1, arr2, ...);

感谢所有为解决此问题做出贡献的人:谢谢。第一轮在我身上!


*除了显而易见的:向浏览器发送更少的数据

6个回答

这就是我要尝试的:你说这是一个 44MB 的文件。这肯定需要超过 44MB 的内存,我猜这需要超过 44MB 的 RAM,可能是半个演出。你能不能把数据删减直到浏览器不崩溃,看看浏览器使用了多少内存?

即使只在服务器上运行的应用程序也不会读取 44MB 的文件并将其保存在内存中。说了这么多,我相信浏览器应该能够处理它,所以让我运行一些测试。

(使用 Windows 7,4GB 内存)

第一次测试 我把阵列切成两半,没有问题,使用 80MB,没有崩溃

第二个测试 我把数组拆分成两个单独的数组,但仍然包含所有数据,使用160Mb,没有崩溃

第三次测试 由于Firefox 说它用完了堆栈,问题可能是它无法立即解析数组。我创建了两个单独的数组,arr1, arr2 然后做了 arr3 = arr1.concat(arr2); 它运行良好,只使用了稍微多一点的内存,大约 165MB。

第四次测试我正在创建其中的 7 个数组(每个 22MB)并将它们连接起来以测试浏览器限制。页面完成加载大约需要 10 秒。内存上升到 1.3GB,然后又下降到 500MB。所以是的 chrome 可以处理它。它只是无法一次全部解析,因为它使用了某种递归,正如控制台的错误消息所指出的那样。

解答创建单独的数组(每个小于 20MB),然后将它们连接起来。每个数组都应该在它自己的 var 语句上,而不是使用单个 var 进行多个声明。

我仍然会考虑只获取必要的部分,这可能会使浏览器变得迟钝。但是,如果是内部任务,这应该没问题。

最后一点:你不是在最大内存级别,只是最大解析级别。

这看起来很有希望。必须尽快尝试。
2021-03-20 07:45:27
我会将所有数组传递给 concat() 而不是多次调用 concat 。我试过你的数据,把它分成两个数组工作。
2021-04-01 07:45:27
我要开始玩这个了。在我走错路之前:你究竟是如何声明和连接数组的?(1) var arr1 = [...], arr2 = [...], arr3 = [...]. bigArr = arr1.concat(arr2).concat(arr3);(2) bigArr = arr1.concat(arr2, arr3);(3) var bigArr = [...].concat([...], [...]); ? 或者我是否需要var为每个子数组单独声明,或者这些都不重要?
2021-04-07 07:45:27
这是我要尝试的:var arr7 = arr0.concat(arr1,arr2,arr3,arr4,arr5,arr6)我认为单独的 var 语句不会产生任何区别。
2021-04-08 07:45:27
测试 1:将数组拆分为 4 个子数组(每个子数组 2^15 elt)并使用bigArr= arr0.concat(arr1).concat(arr2).concat(arr3)仍然制作的 Chrome barf。让我试试你刚刚建议的...
2021-04-10 07:45:27

是的,对浏览器的要求太多了。

如果该数据量已经是数据,但还不是数据,那么该数据量将是可管理的。考虑到浏览器必须在检查语法加起来的同时解析那个巨大的源代码块。一旦解析为有效代码,就必须运行代码以生成实际数组。

因此,所有数据将同时存在于(至少)两个或三个版本中,每个版本都有一定的开销。由于数组文字是单个语句,因此每个步骤都必须包含所有数据。

将数据分成几个较小的数组可能会使浏览器更容易。

是的,让他们分手。但是您可以将它们连接在一起,浏览器就可以了。问题是试图一次全部解析它,它用完了堆栈。看我的回答
2021-03-17 07:45:27
这意味着如果你礼貌地问,问得不多:)
2021-03-21 07:45:27
你的最后一句话,你的字面意思是做类似的事情var chunk1 = [/* first 21k elts */], chunk2 = [/* next 21k elts */], ... chunk10 = [/* last 21k elts*/];吗?
2021-03-23 07:45:27
@马特鲍尔:是的。我什至会将它们放在单独的语句中,即var chunk1 = [...]; var chunk2 = [...]; ....
2021-04-10 07:45:27

您真的需要所有数据吗?您不能使用 AJAX 仅流式传输当前需要的数据吗?与 Google Maps 类似 - 您无法将所有地图数据放入浏览器的内存中,它们仅显示您当前看到的部分。

请记住,在浏览器的内部表示中,40 兆字节的硬数据可以膨胀到更多。例如 JS 解释器可能使用哈希表来实现数组,这会增加额外的内存开销。此外,我希望浏览器同时存储源代码和 JS 内存,仅此一项会使数据量增加一倍。

JS 旨在提供客户端 UI 交互,而不是处理大量数据。

编辑:

顺便说一句,你真的认为用户会喜欢下载 40 兆字节的代码吗?仍然有许多用户无法访问宽带互联网。脚本的执行将暂停,直到所有数据下载完毕。

编辑2:

我看了一下资料。该数组肯定会表示为哈希表。还有许多项目是对象,这将需要引用跟踪......这是额外的内存。

我想如果它是原始数据的简单向量,性能会更好。

EDIT3:当然可以简化数据。其中大部分是重复的字符串,可以以某种方式编码为整数或其他东西。此外,我的 Opera 无法仅显示文本,更不用说解释它了。

EDIT4:忘记 DateTime 对象使用 unix 时代时间戳或字符串,但不要使用对象!

EDIT5:您的处理器无关紧要,因为 JS 是单线程的。你的 RAM 也不重要,大多数浏览器都是 32 位的,所以它们不能使用太多的内存。

EDIT6:尝试将数组索引更改为连续整数(0、1、2、3...)。这可能会使浏览器使用更高效的数组数据结构。您可以使用常量来有效地访问数组项。这将大大减少数组大小。

...而且不要一次全部展开,一次只展开几条记录。虽然我不确定浏览器的内存管理有多好,所以这可能无济于事。
2021-03-18 07:45:27
@Matt:我想知道消除 Date 对象并没有太大区别的事实是否证明@Guffa 的答案可能有value。
2021-03-20 07:45:27
玩弄日期 - 更改为时间戳(自纪元以来的毫秒数) - 没有帮助。不过,也许它可以缓解一小部分问题,并且与其他调整配合使用会很有用。
2021-03-22 07:45:27
+1 为 Edit4;将数据以压缩格式导入,然后在客户端将其扩展为对象。
2021-03-25 07:45:27
即使使用宽带,我也不想加载和保留大约 40MB(原始!)的垃圾:PI 想知道真正的内存使用情况是什么
2021-04-04 07:45:27

尝试使用 Ajax 作为 JSON 页面检索数据。我不知道确切的大小,但我已经能够通过这种方式将大量数据提取到谷歌浏览器中。

就像我说的,虽然可能不够清楚,但我不是在寻找有关如何一次将更少的数据转储到页面中的建议。
2021-03-31 07:45:27
所以基本上,加载相同数量的信息,但使用 Ajax 而不是页面的其余部分?这可能值得一试。
2021-04-07 07:45:27
我不是建议加载更少的数据,而是建议以不同的方式加载它。不是作为脚本的一部分,而是作为单独的 JSON 文档。这取决于浏览器如何实现 JSON 解析,但 Chrome 至少在没有 'eval'-ing 脚本的情况下做到了这一点,它解析 JSON 的效率更高。
2021-04-10 07:45:27

使用延迟加载。拥有指向数据的指针并在用户询问时获取它。

这种技术在很多地方都被用来管理数百万条数据记录。

[编辑]

我找到了我要找的东西。jqgrid 中的虚拟滚动那是 500k 条记录被延迟加载。

你介意扩展一下吗?您是指使用 Ajax 进行延迟加载吗?
2021-04-07 07:45:27
@MattBall 在分页数据和逐块加载和进行预测性延迟加载之间有一个重要区别,即在用户需要数据之前在后台偷偷加载并假装用户没有加载时间,但实际上只是不引人注目。
2021-04-09 07:45:27