繁体   English   中英

为什么数组在 JavaScript 中的最大大小为 2 ^ 32 -1?

[英]Why does array have max size 2 ^ 32 -1 in JavaScript?

JavaScript 中的最大原生整数值为2 ^ 31 - 1 = 2147483647

您可以使用以下代码检查它:

let x = 2147483647;
console.log(x << 1) // you will get -2 instead of 2,147,483,647 * 2 = 4,294,967,296 

这意味着大于2 ^ 31 - 1 = 2147483647的变量始终具有浮点类型。

但是根据ECMAScript的最大数组大小是2 ^ 32 - 1 = 4294967295

所以如果我写类似

let size = 3000000000;       // float type
let array = new Array(size); // passing float variable to constructor
                             // that accepts integer only
 

这意味着首先size将具有浮点类型,然后我们将此浮点大小作为数组大小参数传递。 但是数组大小显然必须是整数。

而且它根本没有任何意义。

所以问题是:为什么 ECMAScript 说最大数组大小是2 ^ 32 - 1而不是2 ^ 31 - 1

Tl;博士:

它从 double 转换为 uint32,这就是为什么 2^32-1 可以工作并且可以在 js 中使用而没有任何问题。


事实上,可以创建一个带有最多 2^32 -1 个数字槽的多孔数组。 超出的任何内容都将转换为字符串哈希索引,并且不会将数组长度属性迭代超过 2^32 -1。

一种方法是:

# node
Welcome to Node.js v16.15.0.
Type ".help" for more information.
> const a = new Array(2**31-1)
undefined
> a
[ <2147483647 empty items> ]
> a.push("foo", "bar", "asdf")
2147483650
> a
[ <2147483647 empty items>, 'foo', 'bar', 'asdf' ]
> a.length
2147483650
> a.length = 2**32 -2
4294967294
> a.push("magic", "beer", "such overflow")
Uncaught RangeError: Invalid array length
    at Array.push (<anonymous>)
> a[2**32]
'such overflow'
> a[2**32 -20] = "indexable beyond 2^31-1"
'indexable beyond 2^31-1'
> a
[
  <2147483647 empty items>,
  'foo',
  'bar',
  'asdf',
  <2147483626 empty items>,
  'indexable beyond 2^31-1',
  <17 empty items>,
  'magic',
  '4294967295': 'beer',
  '4294967296': 'such overflow'
]
> a.length
4294967295

另一个没有初始长度的例子:

# node
Welcome to Node.js v16.15.0.
Type ".help" for more information.
> const a = []
undefined
> a[2**32 -10] = "magic"
'magic'
> a[2**33] = "overflow"
'overflow'
> a
[ <4294967286 empty items>, 'magic', '8589934592': 'overflow' ]
>
> Object.keys(a)
[ '4294967286', '8589934592' ]
> a[5] = "foo"
'foo'
> Object.keys(a)
[ '5', '4294967286', '8589934592' ]
> a
[
  <5 empty items>,
  'foo',
  <4294967280 empty items>,
  'magic',
  '8589934592': 'overflow'
]
> a.length
4294967287

但是如果你填满它,你会用完堆内存:

# node
Welcome to Node.js v16.15.0.
Type ".help" for more information.
> const a = [];
undefined
> a.length = 2**32 -1;
4294967295
> a.fill(0)

<--- Last few GCs --->
n [1537688:0x5818540]    33577 ms: Mark-sweep 238.5 (256.1) -> 238.3 (272.1) MB, 202.6 / 0.0 ms  (+ 0.4 ms in 42 steps since start of marking, biggest step 0.0 ms, walltime since start of marking 833 ms) (average mu = 0.971, current mu = 0.868) allocation f[1537688:0x5818540]    52935 ms: Mark-sweep 1796.1 (1833.3) -> 1796.1 (1833.3) MB, 1996.9 / 0.0 ms  (+ 3.3 ms in 235 steps since start of marking, biggest step 0.1 ms, walltime since start of marking 9982 ms) (average mu = 0.911, current mu = 0.897) alloc

<--- JS stacktrace --->

FATAL ERROR: invalid table size Allocation failed - JavaScript heap out of memory
 1: 0xb09c10 node::Abort() [node]
 2: 0xa1c193 node::FatalError(char const*, char const*) [node]
 3: 0xcf8dbe v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
 4: 0xcf9137 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
 5: 0xeb09d5  [node]
 6: 0x10dcbdd  [node]
 7: 0x10dcdb3 v8::internal::Handle<v8::internal::NumberDictionary> v8::internal::HashTable<v8::internal::NumberDictionary, v8::internal::NumberDictionaryShape>::EnsureCapacity<v8::internal::Isolate>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::NumberDictionary>, int, v8::internal::AllocationType) [node]
 8: 0x10dd3f4 v8::internal::Handle<v8::internal::NumberDictionary> v8::internal::Dictionary<v8::internal::NumberDictionary, v8::internal::NumberDictionaryShape>::Add<v8::internal::Isolate>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::NumberDictionary>, unsigned int, v8::internal::Handle<v8::internal::Object>, v8::internal::PropertyDetails, v8::internal::InternalIndex*) [node]
 9: 0x1005348  [node]
10: 0x108b615 v8::internal::JSObject::AddDataElement(v8::internal::Handle<v8::internal::JSObject>, unsigned int, v8::internal::Handle<v8::internal::Object>, v8::internal::PropertyAttributes) [node]
11: 0x10cf36e v8::internal::Object::AddDataProperty(v8::internal::LookupIterator*, v8::internal::Handle<v8::internal::Object>, v8::internal::PropertyAttributes, v8::Maybe<v8::internal::ShouldThrow>, v8::internal::StoreOrigin) [node]
12: 0x10d3903 v8::internal::Object::SetProperty(v8::internal::LookupIterator*, v8::internal::Handle<v8::internal::Object>, v8::internal::StoreOrigin, v8::Maybe<v8::internal::ShouldThrow>) [node]
13: 0xd5972d v8::internal::Builtin_ArrayPrototypeFill(int, unsigned long*, v8::internal::Isolate*) [node]
14: 0x15f2179  [node]
[1]    1537688 abort (core dumped)  node
node  26,77s user 21,04s system 39% cpu 2:02,24 total

您还可以使用最多 2^32 -1 项初始化 Array:

节点:

> new Array(2**32-1)
[ <4294967295 empty items> ]
> Array(2**32-1)
[ <4294967295 empty items> ]
> 3000000000
3000000000
> Array(3000000000)
[ <3000000000 empty items> ]

火狐:

Array(2**32-1)
(4294967295) [empty × 4294967295]
new Array(2**32-1)
(4294967295) [empty × 4294967295]

你的问题有点误导,因为你让它出现了,好像超过 2^31 的任何东西根本不可能访问,而规范明确声明将参数(如果它是数字类型)转换为 uint32,从而允许你使用一个浮点数超过 2^31 以进行索引。

事实: https ://262.ecma-international.org/12.0/#sec-array Array(...values) 的规范

10.4.2.2 ArrayCreate (length [, proto]) 的规范

因此,本质上,引擎会将您的Number转换为 uint32。 由于 IEE 754(JavaScripts 浮点类型)可以携带超过 2^32-1 的数字,它会简单地转换它。

它对 uint32 进行静态转换,如 v8 所示: https ://github.com/v8/v8/blob/b5283a2e5bc31b254a73f2a0e59841a8654de092/src/builtins/builtins-array.cc#L176-L178

// Set "length" property, has "fast-path" for JSArrays.
// Returns Nothing if something went wrong.
V8_WARN_UNUSED_RESULT MaybeHandle<Object> SetLengthProperty(
    Isolate* isolate, Handle<JSReceiver> receiver, double length) {
  if (receiver->IsJSArray()) {
    Handle<JSArray> array = Handle<JSArray>::cast(receiver);
    if (!JSArray::HasReadOnlyLength(array)) {
      DCHECK_LE(length, kMaxUInt32);
      MAYBE_RETURN_NULL(
          JSArray::SetLength(array, static_cast<uint32_t>(length)));
      return receiver;
    }
  }

  return Object::SetProperty(
      isolate, receiver, isolate->factory()->length_string(),
      isolate->factory()->NewNumber(length), StoreOrigin::kMaybeKeyed,
      Just(ShouldThrow::kThrowOnError));
}

与蜘蛛猴相同:
https://searchfox.org/mozilla-central/rev/32ca4fc265150e7d3d7aa6c6abea088768cf024b/js/src/builtin/Array.cpp#672

// Step 3.
if (!ToUint32(cx, desc.value(), &newLen)) {
  return false;
}

所以你的问题的答案是:

该数组在 Spec 内部使用 uint32(甚至在 5.0 中)以及 SpiderMonkey 和 V8。

它从 double 转换为 uint32,这就是为什么 2^32-1 可以工作并且可以在 js 中使用而没有任何问题。

为什么数组在 JavaScript 中的最大大小为2^32 - 1

很简单:因为它是这样指定的。 规范作者可以选择任何最大值。

2^32 - 1是可以用无符号 32 位整数表示的最大值。 这是一个合理的选择,因为它将存储数组长度所需的内存限制为 32 位,同时最大化可以存储在这 32 位中的长度。

因此,JavaScript 引擎理论上可以使用无符号 32 位整数来存储任何数组长度(以及任何有效的数组索引)。 我不知道是否有任何 JavaScript 引擎实际上是这样做的。 (V8 没有;它使用带符号的 31 位(!)整数或 IEE 754 float64,因为...原因太长,无法在这里解释!)

JavaScript 中的最大原生整数值为2 ^ 31 - 1 = 2147483647 大于2 ^ 31 - 1 = 2147483647的变量,始终具有浮点类型。

不,JavaScript 中的所有数字都指定为 IEE 754 64 位浮点值,JS 中没有“最大本机整数值”之类的东西。 一些操作(即,像x << 1这样的按位操作)将它们的输入转换为有符号的 32 位整数。 FWIW,无符号右移将其输入转换为无符号 32 位整数,并将其输出也解释为,例如(-1 >>> 0) === 4294967295 这种可观察到的行为并不能保证引擎如何选择在机器级别上表示这些值。 无法判断12147483647是在引擎内存储为整数还是浮点数。 JavaScript 规范只保证它的行为像一个浮点数。
而且,为了将其与问题联系起来,按位运算的作用与最大数组长度完全无关。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM