JavaScript配列はスパースですか?


97

つまり、現在の時刻を配列のインデックスとして使用すると、次のようになります。

array[Date.getTime()] = value;

インタプリタは0から現在までのすべての要素をインスタンス化しますか?ブラウザーが異なると、それは異なりますか?

以前は、AIXカーネルにバグがあり、リクエストに応じて疑似ttyが作成されることを覚えていましたが、「echo> / dev / pty10000000000」とすると、/ dev / pty0、/ dev / pty1が作成されます....そして、倒れる。見本市では楽しかったですが、これが私の顧客に起こってほしくありません。


1
これを行うことの可能な欠点は、Firebugでのデバッグが難しいことです。配列のログステートメントは、配列内の最初の1000個の要素のみをリストします。これらはすべて「未定義」になります。また、array.lengthは、n-1が単に「ゴースト」の未定義の値であっても、配列にn個の要素があることを通知します。
マイケルバトラー

Chromeでのデバッグが可能になりました-コンソール出力の例を次に示します:[empty×9564、Object、empty×105、Object、empty×10、Object、empty×12、Object、empty×9、Object、empty×21、オブジェクト、空×9、オブジェクト]
jsalvata

回答:


40

JavaScript配列の実装方法はブラウザーによって異なりますが、実際の配列の使用が非効率的である場合、通常はスパース実装(通常のオブジェクトのプロパティアクセスに使用されるものと同じ)にフォールバックします。

特定の実装についてもっと知識のある人に、密から疎へのシフトを引き起こすきっかけとなるものに答えるように依頼する必要がありますが、あなたの例は完全に安全でなければなりません。密な配列を取得する場合は、明示的な長さの引数を指定してコンストラクターを呼び出し、実際に取得することを望んでください。

olliejによる詳細な説明については、この回答を参照してください。


1
のように言っても、実際には密な配列を取得するとは思わないfoo = new Array(10000)。ただし、これは機能するはずですfoo = Array.apply(null, {length: 10});
doubleOrt 2017年

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:フラグが設定されているためlengthfor..inループ内でのみ非表示になりますDontEnum。ES5では、プロパティ属性が呼び出されenumerable、明示的に設定できますObject.defineProperty()
Christoph

14
JavaScriptのすべてのオブジェクトキーは常にStringです。下付き文字に入れた他のものはすべて取得されtoString()ます。これを大きな数値の不正確な整数と組み合わせると、を設定した場合a[9999999999999999]=1a[10000000000000000]1 になります(さらに多くの驚くべき動作)。非整数をキーとして使用することは非常に賢明ではなく、任意のオブジェクトはすぐに使用できます。
ボビンス、2009年

71
次に、文字列をオブジェクトキーとしてのみ使用する必要があります。ひもはあなたが使用すべきタイプであり、鍵のタイプはひもでなければならない。整数は使用しないでください。使用しない場合は整数ではありません。ただし、文字列にキャストする場合を除きます。任意のオブジェクトはすぐに使用できます。
クレセントフレッシュ

8
配列のインデックスは整数でなければなりません。array [3.14] = piは、ArrayがObjectから継承されるため機能します。例:var x = []; x [.1] = 5; その場合、xの長さは0のままです。
マイクブランドフォード、

10

この種のもののために設計されたJavaScript構文を使用することにより、この問題を回避できます。辞書として扱うこともできますが、「for ... in ...」構文を使用すると、すべてを取得できます。

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

7

JavaScriptオブジェクトはスパースであり、配列は、自動維持される長さプロパティ(実際には、定義された要素の数ではなく、最大のインデックスより1つ大きい)といくつかの追加メソッドを備えた特殊なオブジェクトです。あなたはどちらにしても安全です。追加機能が必要な場合は配列を使用し、それ以外の場合はオブジェクトを使用します。


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カーネルのバグの感触を得るために、範囲と同様に強制する必要があるかもしれません。

> 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歳の質問がまだ関連していることにちょっと驚いています。
ベリー

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月のものであり、内容は多少変更される可能性がありますが、日々の開発への影響の内訳は有用かつ明確です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.