Javascript数组稀疏吗?


97

也就是说,如果我将当前时间用作数组的索引:

array[Date.getTime()] = value;

解释器将实例化从0到现在的所有元素吗?不同的浏览器会做不同的事情吗?

我记得AIX内核中曾经有一个错误,该错误会在请求时创建伪tty,但是如果您说“ echo> / dev / pty10000000000”,它将创建/ dev / pty0,/ dev / pty1, ....然后倒下死亡。在贸易展览会上这很有趣,但是我不希望这种情况发生在我的客户身上。


1
这样做的可能弊端是在Firebug中进行调试的难度。数组上的log语句仅列出数组中的前1000个元素,所有元素均为“未定义”。另外,即使n-1只是“鬼”未定义的值,array.length也会告诉您数组中有n个元素。
Michael Butler

现在可以在Chrome中进行调试了-这是控制台输出的示例:[空×9564,对象,空×105,对象,空×10,对象,空×12,对象,空×9,对象,空×21,对象,空×9,对象]
jsalvata,

Answers:


40

JavaScript数组的确切实现方式因浏览器而异,但通常归结为稀疏实现-如果使用实际数组效率低下,则最有可能用于常规对象的属性访问。

您必须问一个对特定实现有更多了解的人,以回答是什么触发了从密集到稀疏的转变,但是您的示例应该是完全安全的。如果要获得密集数组,则应使用显式的length参数调用构造函数,并希望实际上得到一个。

请参阅此答案,以获得olliej的更详细描述。


1
如果您说类似,我认为您实际上并不会得到密集的数组foo = new Array(10000)。然而,这应该工作:foo = Array.apply(null, {length: 10});
doubleOrt

70

对,他们是。它们实际上是内部的哈希表,因此您不仅可以使用大整数,还可以使用字符串,浮点数或其他对象。所有键toString()在添加到哈希之前都将通过转换为字符串。您可以使用一些测试代码来确认这一点:

<script>
  var array = [];
  array[0] = "zero";
  array[new Date().getTime()] = "now";
  array[3.14] = "pi";

  for (var i in array) {
      alert("array["+i+"] = " + array[i] + ", typeof("+i+") == " + typeof(i));
  }
</script>

显示:

array[0] = zero, typeof(0) == string
array[1254503972355] = now, typeof(1254503972355) == string
array[3.14] = pi, typeof(3.14) == string

请注意,我是如何使用for...in语法的,该语法仅为您提供实际定义的索引。如果使用更通用for (var i = 0; i < array.length; ++i)的迭代样式,那么显然非标准数组索引会出现问题。


9
如果可能,大多数JS实现会将数字索引属性存储在实际数组中;不过,这是幕后魔术:从语言的角度来看,数组是具有魔术length属性的常规对象
Christoph

7
@John:length仅在for..in循环中不可见,因为它DontEnum设置了标志;在ES5中,调用了property属性enumerable,可以通过Object.defineProperty()
Christoph

14
JavaScript中的所有对象键始终是String;您toString()在下标中放入的其他所有内容都将-ed。将其与大数的整数不精确度结合使用,这意味着如果将设置为a[9999999999999999]=1a[10000000000000000]则将为1(还有更多令人惊讶的行为)。使用非整数作为键是非常不明智的,并且任意对象都是正确的。
2009年

71
然后,您只能使用Strings作为对象键,不多也不少。字符串应为您将使用的类型,密钥的类型应为字符串。您不应该使用整数,也不要使用非整数,除非您随后要强制转换为字符串。任意对象都是正确的。

8
数组索引必须是整数。array [3.14] = pi有效,因为Array来自Object。例如:var x = []; x [.1] = 5; 然后x的长度仍然为0。
Mike Blandford

10

您可以通过使用针对此类问题设计的javascript语法来避免此问题。您可以将其视为字典,但是“ for ... in ...”语法可让您抓住它们。

var sparse = {}; // not []
sparse["whatever"] = "something";

7

Javascript对象是稀疏的,而数组只是具有自动维护的length属性(实际上比最大索引大一个,而不是已定义元素的数量)的专门对象和一些其他方法。无论哪种方式,您都是安全的;如果需要,可以使用数组,否则使用对象。


4
从语言的角度来看;实现实际上使用实数组来存储密集的数字属性
Christoph

6

答案(通常与JavaScript一样)是“有点奇怪……”。

没有定义内存使用情况,并且任何实现都可以是愚蠢的。从理论上讲,const a = []; a[1000000]=0;可以刻录兆字节的内存const a = [];。实际上,即使Microsoft也避免了这些实现。

贾斯汀·洛夫指出,长度属性是最高的索引集。但是,仅当索引为整数时,才更新它。

因此,数组是稀疏的。但是,诸如reduce(),Math.max()和“ for ... of”之类的内置函数将遍历整个可能的整数索引范围,从0到长度,访问许多返回“ undefined”的整数。但是“ for ... in”循环可能会按您期望的那样运行,只访问定义的键。

这是使用Node.js的示例:

"use strict";
const print = console.log;

let a = [0, 10];
// a[2] and a[3] skipped
a[4] = 40;
a[5] = undefined;  // which counts towards setting the length
a[31.4] = 'ten pi';  // doesn't count towards setting the length
a['pi'] = 3.14;
print(`a.length= :${a.length}:, a = :${a}:`);
print(`Math.max(...a) = :${Math.max(a)}: because of 'undefined values'`);
for (let v of a) print(`v of a; v=:${v}:`);
for (let i in a) print(`i in a; i=:${i}: a[i]=${a[i]}`);

给予:

a.length= :6:, a = :0,10,,,40,:
Math.max(...a) = :NaN: because of 'undefined values'
v of a; v=:0:
v of a; v=:10:
v of a; v=:undefined:
v of a; v=:undefined:
v of a; v=:40:
v of a; v=:undefined:
i in a; i=:0: a[i]=0
i in a; i=:1: a[i]=10
i in a; i=:4: a[i]=40
i in a; i=:5: a[i]=undefined
i in a; i=:31.4: a[i]=ten pi
i in a; i=:pi: a[i]=3.14

但。还有更多未提及的极端情况。


2

可以通过非标准process.memoryUsage()凭经验确定NodeJS的稀疏性(或密度

有时节点足够聪明,可以使数组稀疏:

Welcome to Node.js v12.15.0.
Type ".help" for more information.
> console.log(`The script is using approximately ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100} MB`)
The script is using approximately 3.07 MB
undefined
> array = []
[]
> array[2**24] = 2**24
16777216
> array
[ <16777216 empty items>, 16777216 ]
> console.log(`The script is using approximately ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100} MB`)
The script is using approximately 2.8 MB
undefined

有时节点选择使其密集(此行为将来可能会得到优化):

> otherArray = Array(2**24)
[ <16777216 empty items> ]
> console.log(`The script is using approximately ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100} MB`)
The script is using approximately 130.57 MB
undefined

然后再稀疏:

> yetAnotherArray = Array(2**32-1)
[ <4294967295 empty items> ]
> console.log(`The script is using approximately ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100} MB`)
The script is using approximately 130.68 MB
undefined

因此,也许需要使用密集数组来了解原始的AIX内核错误,可能需要使用range-alike来强制执行:

> denseArray = [...Array(2**24).keys()]
[
   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,
  12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
  24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
  36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
  60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
  72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
  84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
  96, 97, 98, 99,
  ... 16777116 more items
]
> console.log(`The script is using approximately ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100} MB`);
The script is using approximately 819.94 MB
undefined

因为为什么不让它跌倒?

> tooDenseArray = [...Array(2**32-1).keys()]

<--- Last few GCs --->

[60109:0x1028ca000]   171407 ms: Scavenge 1072.7 (1090.0) -> 1056.7 (1090.0) MB, 0.2 / 0.0 ms  (average mu = 0.968, current mu = 0.832) allocation failure 
[60109:0x1028ca000]   171420 ms: Scavenge 1072.7 (1090.0) -> 1056.7 (1090.0) MB, 0.2 / 0.0 ms  (average mu = 0.968, current mu = 0.832) allocation failure 
[60109:0x1028ca000]   171434 ms: Scavenge 1072.7 (1090.0) -> 1056.7 (1090.0) MB, 0.2 / 0.0 ms  (average mu = 0.968, current mu = 0.832) allocation failure 


<--- JS stacktrace --->

==== JS stack trace =========================================

    0: ExitFrame [pc: 0x100931399]
    1: StubFrame [pc: 0x1008ee227]
    2: StubFrame [pc: 0x100996051]
Security context: 0x1043830808a1 <JSObject>
    3: /* anonymous */ [0x1043830b6919] [repl:1] [bytecode=0x1043830b6841 offset=28](this=0x104306fc2261 <JSGlobal Object>)
    4: InternalFrame [pc: 0x1008aefdd]
    5: EntryFrame [pc: 0x1008aedb8]
    6: builtin exit frame: runInThisContext(this=0x104387b8cac1 <ContextifyScript map = 0x1043...

FATAL ERROR: invalid array length Allocation failed - JavaScript heap out of memory

Writing Node.js report to file: report.20200220.220620.60109.0.001.json
Node.js report completed
 1: 0x10007f4b9 node::Abort() [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 2: 0x10007f63d node::OnFatalError(char const*, char const*) [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 3: 0x100176a27 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 4: 0x1001769c3 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 5: 0x1002fab75 v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 6: 0x1005f3e9b v8::internal::Runtime_FatalProcessOutOfMemoryInvalidArrayLength(int, unsigned long*, v8::internal::Isolate*) [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 7: 0x100931399 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 8: 0x1008ee227 Builtins_IterableToList [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
Abort trap: 6

1
很好,我很惊讶我十岁的问题仍然有意义!
贝里

1

可以,但不一定总是如此,当不是时,它们可以表现更好。

这是关于如何测试数组实例中索引稀疏的讨论:https : //benmccormick.org/2018/06/19/code-golf-sparse-arrays/

该代码高尔夫(最低字符)获胜者是:

let isSparse = a => !!a.reduce(x=>x-1,a.length)

基本上是遍历数组以获取索引条目,同时递减长度值并返回!!伪造/真实数值结果的布尔值(如果累加器一直递减为零,则索引将完全填充且不稀疏)。也应考虑Charles Merriam的上述警告,此代码未解决这些警告,但它们适用于哈希字符串条目,当使用arr[var]= (something) var不是整数。

关心索引稀疏性的原因在于它对性能的影响,这在脚本引擎之间可能会有所不同,这里对数组创建/。初始化进行了大量讨论: 声明JavaScript时“ Array()”和“ []”之间有什么区别?数组?

该帖子的最新答案与这个深入的链接有关,深入探讨了V8如何通过标记数组来优化数组,从而避免(重新)测试稀疏性等特性:https : //v8.dev/blog/elements-kinds。该博客文章来自17年9月,材料可能会有所更改,但对于日常开发的影响细分是有用且清晰的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.