Apakah larik Javascript jarang?

97

Artinya, jika saya menggunakan waktu saat ini sebagai indeks ke dalam array:

array[Date.getTime()] = value;

akankah penerjemah memberi contoh semua elemen dari 0 hingga sekarang? Apakah browser yang berbeda melakukannya secara berbeda?

Saya ingat dulu ada bug di kernel AIX , yang akan membuat pseudo-ttys berdasarkan permintaan, tetapi jika Anda melakukannya, katakan, "echo> / dev / pty10000000000" itu akan membuat / dev / pty0, / dev / pty1, .... dan kemudian jatuh mati. Itu menyenangkan di pameran dagang, tetapi saya tidak ingin ini terjadi pada pelanggan saya.

Berry
sumber
1
Kemungkinan kerugian untuk melakukan ini adalah kesulitan dalam men-debug di Firebug. pernyataan log pada larik hanya akan mencantumkan 1000 elemen pertama dalam larik, yang semuanya akan "tidak ditentukan". Selain itu, array.length akan memberi tahu Anda bahwa array Anda memiliki n elemen di dalamnya, meskipun n-1 hanyalah nilai tidak terdefinisi "hantu".
Michael Butler
Debugging sekarang OK di Chrome - berikut adalah contoh keluaran konsol: [kosong × 9564, Objek, kosong × 105, Objek, kosong × 10, Objek, kosong × 12, Objek, kosong × 9, Objek, kosong × 21, Objek, kosong × 9, Objek]
jsalvata

Jawaban:

40

Bagaimana sebenarnya larik JavaScript diimplementasikan berbeda dari satu peramban ke peramban lainnya, tetapi umumnya kembali ke penerapan renggang - kemungkinan besar sama yang digunakan untuk akses properti objek biasa - jika menggunakan larik sebenarnya tidak efisien.

Anda harus meminta seseorang yang memiliki lebih banyak pengetahuan tentang implementasi spesifik untuk menjawab apa yang memicu pergeseran dari padat ke jarang, tetapi contoh Anda harus benar-benar aman. Jika Anda ingin mendapatkan larik padat, Anda harus memanggil konstruktor dengan argumen panjang eksplisit dan berharap Anda benar-benar mendapatkannya.

Lihat jawaban ini untuk penjelasan lebih rinci oleh olliej.

Christoph
sumber
1
Saya tidak berpikir Anda benar-benar mendapatkan array yang padat jika Anda mengatakan sesuatu seperti foo = new Array(10000). Namun, ini seharusnya pekerjaan: foo = Array.apply(null, {length: 10});.
doubleOrt
70

Ya begitulah. Mereka sebenarnya adalah tabel hash secara internal, sehingga Anda tidak hanya dapat menggunakan bilangan bulat besar tetapi juga string, float, atau objek lainnya. Semua kunci diubah menjadi string melalui toString()sebelum ditambahkan ke hash. Anda dapat mengonfirmasi ini dengan beberapa kode tes:

<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>

Menampilkan:

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

Perhatikan bagaimana saya menggunakan for...insintaks, yang hanya memberi Anda indeks yang sebenarnya didefinisikan. Jika Anda menggunakan for (var i = 0; i < array.length; ++i)gaya iterasi yang lebih umum maka Anda jelas akan memiliki masalah dengan indeks array non-standar.

John Kugelman
sumber
9
kebanyakan implementasi JS menyimpan properti yang diindeks secara numerik dalam larik aktual jika memungkinkan; Itulah keajaiban di balik layar, meskipun: dari sudut pandang bahasa, array adalah objek biasa dengan lengthproperti ajaib
Christoph
7
@ John: lengthhanya tidak terlihat dalam for..inloop karena DontEnumflag memiliki set; di ES5, atribut properti dipanggil enumerabledan dapat diatur secara eksplisit melaluiObject.defineProperty()
Christoph
14
Semua kunci objek di JavaScript selalu String; apa pun yang Anda masukkan ke dalam subskrip mendapat toString()-ed. Gabungkan ini dengan ketidaktepatan integer dari Angka besar dan itu berarti jika Anda mengatur a[9999999999999999]=1, a[10000000000000000]akan menjadi 1 (dan banyak lagi perilaku yang mengejutkan). Menggunakan non-integer sebagai kunci sangat tidak bijaksana, dan objek arbitrer benar.
bobince
72
Maka Anda hanya akan menggunakan String sebagai kunci objek, tidak lebih, tidak kurang. String adalah tipe yang akan Anda gunakan, dan tipe kuncinya adalah String. Integer tidak boleh Anda gunakan, juga tidak menggunakan non-integer, kecuali Anda kemudian melanjutkan untuk melemparkan ke String. Objek sewenang-wenang langsung keluar.
Crescent Fresh
8
Indeks array harus bilangan bulat. array [3.14] = pi bekerja karena Array berada di dalamnya dari Object. Contoh: var x = []; x [.1] = 5; Maka x memiliki panjang 0 diam.
Mike Blandford
10

Anda dapat menghindari masalah ini dengan menggunakan sintaks javascript yang dirancang untuk hal semacam ini. Anda dapat memperlakukannya sebagai kamus, namun sintaks "untuk ... dalam ..." akan memungkinkan Anda mengambil semuanya.

var sparse = {}; // not []
sparse["whatever"] = "something";
John Fisher
sumber
7

Objek JavaScript jarang, dan array hanyalah objek khusus dengan properti panjang yang dipertahankan otomatis (yang sebenarnya lebih besar dari indeks terbesar, bukan jumlah elemen yang ditentukan) dan beberapa metode tambahan. Anda aman dengan cara apa pun; menggunakan array jika Anda membutuhkan fitur tambahannya, dan sebaliknya menggunakan objek.

Justin Love
sumber
4
itu dari sudut pandang bahasa; implementasi sebenarnya menggunakan array nyata untuk menyimpan properti numerik padat
Christoph
6

Jawabannya, seperti yang biasanya terjadi pada JavaScript, adalah "ini sedikit lebih aneh ...."

Penggunaan memori tidak ditentukan dan implementasi apa pun boleh dianggap bodoh. Secara teori, const a = []; a[1000000]=0;bisa membakar megabyte memori, seperti bisa const a = [];. Dalam praktiknya, bahkan Microsoft menghindari penerapan tersebut.

Justin Love menunjukkan, atribut panjang adalah kumpulan indeks tertinggi . TAPI hanya diperbarui jika indeksnya adalah bilangan bulat.

Jadi, arraynya jarang. TAPI fungsi bawaan seperti reduce (), Math.max (), dan "for ... of" akan berjalan melalui seluruh rentang kemungkinan indeks integer dari 0 ke panjang, mengunjungi banyak yang mengembalikan 'tidak ditentukan'. TAPI 'untuk ... dalam' loop mungkin melakukan seperti yang Anda harapkan, hanya mengunjungi kunci yang ditentukan.

Berikut ini contoh menggunakan 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]}`);

memberi:

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

Tapi. Ada lebih banyak kasus sudut dengan Array belum disebutkan.

Charles Merriam
sumber
2

Ketersebaran (atau kepadatan) dapat dikonfirmasi secara empiris untuk NodeJS dengan proses non-standar . MemoriUsage () .

Terkadang node cukup pintar untuk menjaga array tetap jarang:

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

Terkadang node memilih untuk membuatnya padat (perilaku ini mungkin dioptimalkan di masa mendatang):

> 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

Lalu jauhkan lagi:

> 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

Jadi mungkin menggunakan larik padat untuk merasakan bug kernel AIX asli mungkin perlu dipaksa dengan rentang yang mirip :

> 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

Karena kenapa tidak membuatnya jatuh?

> 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
pzrq.dll
sumber
1
Bagus, dan saya agak heran pertanyaan saya yang berumur sepuluh tahun masih relevan!
Berry
1

Mereka bisa jadi tapi tidak selalu harus begitu, dan mereka bisa berkinerja lebih baik padahal tidak.

Berikut adalah pembahasan tentang cara menguji ketersebaran indeks dalam instance array: https://benmccormick.org/2018/06/19/code-golf-sparse-arrays/

Pemenang kode golf (karakter paling sedikit) ini adalah:

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

Pada dasarnya menjalankan array untuk entri yang diindeks sambil mengurangi nilai panjang dan mengembalikan !!boolean yang diperkeras dari hasil numerik falsy / truthy (jika akumulator diturunkan hingga nol, indeks terisi penuh dan tidak tersebar). Peringatan Charles Merriam di atas harus dipertimbangkan juga dan kode ini tidak membahasnya, tetapi berlaku untuk entri string berciri yang dapat terjadi saat menugaskan elemen denganarr[var]= (something) var yang bukan merupakan integer.

Alasan untuk peduli tentang ketersebaran indeks adalah pengaruhnya terhadap kinerja, yang dapat berbeda di antara mesin skrip, ada diskusi hebat tentang pembuatan larik / .inisialisasi di sini: Apa perbedaan antara "Array ()" dan "[]" saat mendeklarasikan JavaScript Himpunan?

Jawaban baru-baru ini untuk postingan tersebut memiliki link ke penjelasan mendalam tentang cara V8 mencoba mengoptimalkan array dengan menandai mereka untuk menghindari pengujian (ulang) untuk karakteristik seperti ketersebaran: https://v8.dev/blog/elements-kinds . Entri blog tersebut dari September '17 dan materinya dapat berubah, tetapi uraian implikasi untuk pengembangan sehari-hari berguna dan jelas.

dkloke.dll
sumber