Javascript 배열이 희소합니까?


97

즉, 현재 시간을 배열의 인덱스로 사용하는 경우 :

array[Date.getTime()] = value;

인터프리터는 0부터 지금까지 모든 요소를 ​​인스턴스화합니까? 브라우저마다 다르게 작동합니까?

요청에 따라 의사 -ttys를 생성 하는 AIX 커널에 버그가 있었던 것을 기억합니다 . 그러나 "echo> / dev / pty10000000000"이라고 말하면 / dev / pty0, / dev / pty1이 생성됩니다. .... 그리고 죽어 넘어집니다. 무역 박람회에서는 재미 있었지만 고객에게 이런 일이 일어나지 않기를 바랍니다.


1
이 작업의 가능한 단점은 Firebug에서 디버깅이 어렵다는 것입니다. 배열의 로그 문은 배열의 처음 1000 개 요소 만 나열하며 모두 "정의되지 않음"입니다. 또한 array.length는 n-1이 정의되지 않은 "고스트"값 일지라도 배열에 n 개의 요소가 있음을 알려줍니다.
Michael Butler

이제 Chrome에서 디버깅이 가능합니다. 콘솔 출력의 예는 다음과 같습니다. [빈 × 9564, 개체, 빈 × 105, 개체, 빈 × 10, 개체, 빈 × 12, 개체, 빈 × 9, 개체, 빈 × 21, Object, empty × 9, Object]
jsalvata

답변:


40

JavaScript 배열이 정확히 구현되는 방식은 브라우저마다 다르지만 일반적으로 실제 배열을 사용하는 것이 비효율적이라면 일반 객체의 속성 액세스에 사용되는 것과 동일한 희소 구현으로 대체됩니다.

특정 구현에 대해 더 많은 지식을 가진 사람에게 조밀에서 희소로의 전환을 유발하는 원인에 대한 답변을 요청해야하지만 예제는 완벽하게 안전해야합니다. 조밀 한 배열을 얻으려면 명시적인 길이 인수를 사용하여 생성자를 호출하고 실제로 하나를 얻길 바랍니다.

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에서만 보이지 않습니다 . ES5에서는 속성 속성이 호출 되고 다음을 통해 명시 적으로 설정할 수 있습니다.for..inDontEnumenumerableObject.defineProperty()
Christoph

14
JavaScript의 모든 객체 키는 항상입니다 String. 아래 첨자에 입력 한 다른 모든 항목은 toString()-ed가됩니다. 많은 수의 정수의 부정확성과이 결합하고 사용자가 설정 한 경우 의미 a[9999999999999999]=1, a[10000000000000000]1이됩니다 (그리고 더 많은 놀라운 행동). 정수가 아닌 키를 키로 사용하는 것은 매우 현명하지 않으며 임의의 개체는 바로 사용할 수 있습니다.
bobince 09.10.02

72
그런 다음 문자열을 객체 키로 만 사용해야합니다. 그 이상도 그 이하도 아닙니다. String은 사용할 유형이고 키의 유형은 String입니다. 정수는 사용하지 말아야하며 정수가 아닌 것을 사용하지 말아야합니다. 단, 문자열로 캐스팅하는 것을 제외하고는 말입니다. 임의의 개체가 바로 나옵니다.
Crescent Fresh

8
배열 인덱스는 정수 여야합니다. array [3.14] = pi는 Array가 Object에서 상속되기 때문에 작동합니다. 예 : var x = []; x [.1] = 5; 그러면 x의 길이는 여전히 0입니다.
Mike Blandford

10

이런 종류의 것을 위해 설계된 자바 스크립트 구문을 사용하여 문제를 피할 수 있습니다. 사전으로 취급 할 수 있지만 "for ... in ..."구문을 사용하면 모든 것을 얻을 수 있습니다.

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

7

자바 스크립트 객체는 희소하고 배열은 자동 유지되는 길이 속성 (실제로 는 정의 된 요소 수가 아니라 가장 큰 인덱스보다 하나 더 큼 )과 몇 가지 추가 메서드 가있는 특수 객체입니다 . 당신은 어느 쪽이든 안전합니다. 추가 기능이 필요한 경우 배열을 사용하고 그렇지 않으면 객체를 사용하십시오.


4
그것은 언어 관점에서 나온 것입니다. 구현은 실제로 실제 배열을 사용하여 고밀도 숫자 속성을 저장합니다
Christoph

6

대답은 일반적으로 JavaScript에서 그렇듯이 "조금 더 현명합니다 ...."입니다.

메모리 사용량은 정의되지 않았으며 모든 구현은 어리석은 것이 허용됩니다. 이론적으로 const a = []; a[1000000]=0;는 메가 바이트의 메모리를 태울 수 const a = [];있습니다. 실제로 Microsoft조차도 이러한 구현을 피합니다.

Justin Love 는 길이 속성이 가장 높은 인덱스 세트라고 지적합니다 . 그러나 인덱스가 정수인 경우에만 업데이트됩니다.

따라서 배열은 희소합니다. 그러나 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 커널 버그에 대한 느낌을 얻기 위해 조밀 한 배열을 사용하는 것은 범위와 유사한 방식 으로 강제해야 할 수도 있습니다 .

> 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
훌륭합니다. 10 년 된 제 질문이 여전히 관련이 있다는 것에 놀랐습니다!
Berry

1

그럴 수 있지만 항상 그럴 필요는 없으며 그렇지 않을 때 더 잘 수행 할 수 있습니다.

다음은 배열 인스턴스에서 인덱스 희소성을 테스트하는 방법에 대한 설명입니다. https://benmccormick.org/2018/06/19/code-golf-sparse-arrays/

이 코드 골프 (최소 문자) 우승자는 다음과 같습니다.

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

기본적으로 길이 값을 !!줄이고 거짓 / 진정한 숫자 결과 의 강화 된 부울을 반환하면서 인덱스 된 항목에 대한 배열을 탐색합니다 (누산기가 0까지 감소하면 인덱스가 완전히 채워지고 희소하지 않음). 위의 Charles Merriam의주 의 사항도 고려해야하며이 코드는이를 해결하지 않지만 arr[var]= (something)var가 정수가 아닌 요소를 할당 할 때 발생할 수있는 해시 된 문자열 항목에 적용됩니다 .

인덱스 희소성에 관심이있는 이유는 스크립트 엔진마다 다를 수있는 성능에 대한 영향입니다. 여기에 배열 생성 /. 초기화에 대한 훌륭한 토론이 있습니다. JavaScript를 선언하는 동안 "Array ()"와 "[]"의 차이점은 무엇입니까? 정렬?

해당 게시물에 대한 최근 답변에는 V8이 희소성 ( https://v8.dev/blog/elements-kinds) 과 같은 특성에 대한 (재) 테스트를 피하기 위해 태그를 지정하여 어레이를 최적화하는 방법에 대한 링크가 있습니다 . 블로그 게시물은 '17 년 9 월에 작성되었으며 자료는 일부 변경 될 수 있지만 일상적인 개발에 대한 의미에 대한 분석은 유용하고 명확합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.