Javascript dizileri seyrek mi?


99

Yani, dizinin indeksi olarak şimdiki zamanı kullanırsam:

array[Date.getTime()] = value;

yorumlayıcı 0'dan şimdiye kadar tüm öğeleri somutlaştıracak mı? Farklı tarayıcılar bunu farklı mı yapıyor?

Eskiden AIX çekirdeğinde, istek üzerine sahte tty'ler oluşturan bir hata olduğunu hatırlıyorum , ancak yaparsanız, "echo> / dev / pty10000000000" derseniz, / dev / pty0, / dev / pty1 oluşturacaktı, .... ve sonra düşüp ölür. Ticaret fuarlarında eğlenceliydi ama bunun müşterilerime olmasını istemiyorum.


1
Bunu yapmanın olası bir dezavantajı, Firebug'da hata ayıklamanın zorluğudur. dizideki bir günlük ifadesi yalnızca dizideki ilk 1000 öğeyi listeler ve bunların tümü "tanımsız" olur. Ayrıca, array.length size dizinizin içinde n öğe olduğunu söyleyecektir, n-1 sadece "hayalet" tanımlanmamış değerler olsa bile.
Michael Butler

Hata ayıklama artık Chrome'da tamam - işte bir konsol çıktısı örneği: [boş × 9564, Nesne, boş × 105, Nesne, boş × 10, Nesne, boş × 12, Nesne, boş × 9, Nesne, boş × 21, Object, empty × 9, Object]
jsalvata

Yanıtlar:


40

JavaScript dizilerinin tam olarak nasıl uygulandığı tarayıcıdan tarayıcıya farklılık gösterir, ancak genellikle seyrek bir uygulamaya geri dönerler - büyük olasılıkla normal nesnelerin özellik erişimi için kullanılanla aynıdır - eğer gerçek bir dizi kullanmak verimsiz olacaksa.

Spesifik uygulamalar hakkında daha fazla bilgiye sahip birinden yoğuntan seyreğe geçişi heyecan verici şekilde tetikleyen şeyi yanıtlamasını istemeniz gerekecek, ancak örneğiniz tamamen güvenli olmalıdır. Yoğun bir dizi elde etmek istiyorsanız, kurucuyu açık bir uzunluk argümanıyla çağırmalı ve gerçekten bir tane alacağınızı ummalısınız.

Olliej tarafından daha ayrıntılı bir açıklama için bu yanıta bakın .


1
Böyle bir şey söylersen, gerçekten yoğun bir dizi elde edeceğini sanmıyorum foo = new Array(10000). Ancak, bu işin gerekiyordu: foo = Array.apply(null, {length: 10});.
doubleOrt

71

Evet onlar. Bunlar aslında dahili olarak karma tablolardır, bu nedenle yalnızca büyük tam sayıları değil, aynı zamanda dizeleri, kayan sayıları veya diğer nesneleri de kullanabilirsiniz. Tüm anahtarlar toString(), karmaya eklenmeden önce dizelere dönüştürülür . Bunu bazı test kodlarıyla onaylayabilirsiniz:

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

Görüntüler:

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

for...inSözdizimini nasıl kullandığıma dikkat edin , bu size yalnızca gerçekte tanımlanmış dizinleri verir. Daha yaygın for (var i = 0; i < array.length; ++i)yineleme stilini kullanırsanız, standart olmayan dizi indekslerinde açıkça sorun yaşarsınız.


9
Çoğu JS uygulaması, mümkünse gerçek bir dizide sayısal olarak dizinlenmiş özellikleri depolar; bu perde arkası sihir olsa da: dil açısından, diziler sihirli bir lengthözelliğe sahip normal nesnelerdir
Christoph

7
@John: bayrak ayarlı lengtholduğu için yalnızca for..indöngülerde görünmez DontEnum; ES5'te, özellik özniteliği çağrılır enumerableve açıkça şu şekilde ayarlanabilirObject.defineProperty()
Christoph

14
JavaScript'teki tüm nesne anahtarları her zaman String; alt toString()simgeye koyduğunuz her şey -ed olur . Büyük Sayı tamsayı belirsizlik ile birleştirmek ve ayarlarsanız bu demektir a[9999999999999999]=1, a[10000000000000000]1 olacaktır (ve daha pek çok şaşırtıcı davranışlar). Tamsayı olmayanları anahtar olarak kullanmak çok yanlıştır ve keyfi nesneler hemen çıkar.
bobince

73
Sonra, Dizeleri yalnızca nesne anahtarları olarak kullanacaksınız, ne fazla ne de az. String, kullanacağınız tipte olacaktır ve anahtarın tipi String olacaktır. Tamsayı kullanmayacaksın, tamsayı olmayanları da kullanmayacaksın, ancak daha sonra String'e çevirmeye devam edeceksin. Keyfi nesneler hemen çıktı.
Crescent Fresh

8
Dizi dizinleri tam sayı olmalıdır. array [3.14] = pi çalışır, çünkü Dizi Object'ten gelir. Örnek: var x = []; x [.1] = 5; O zaman x'in uzunluğu 0'dır.
Mike Blandford

10

Bu tür şeyler için tasarlanmış bir javascript sözdizimi kullanarak sorunu önleyebilirsiniz. Bunu bir sözlük olarak ele alabilirsiniz, ancak "for ... in ..." sözdizimi hepsini almanıza izin verir.

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

7

Javascript nesneleri seyrektir ve diziler, otomatik olarak korunan uzunluk özelliğine ( tanımlanmış öğelerin sayısı değil , aslında en büyük dizinden bir büyüktür ) ve bazı ek yöntemlere sahip özelleştirilmiş nesnelerdir . Her iki şekilde de güvendesin; Ekstra özelliklere ihtiyacınız varsa bir dizi, aksi takdirde bir nesne kullanın.


4
bu bir dil bakış açısından; uygulamalar aslında yoğun sayısal özellikleri depolamak için gerçek dizileri kullanır
Christoph

6

Cevap, JavaScript'te genellikle doğru olduğu gibi, "biraz daha zor ..."

Bellek kullanımı tanımlanmaz ve herhangi bir uygulamanın aptal olmasına izin verilir. Teorik olarak, const a = []; a[1000000]=0;megabaytlarca bellek yakabilir const a = [];. Pratikte Microsoft bile bu uygulamalardan kaçınır.

Justin Love işaret ediyor, uzunluk özelliği en yüksek indeks setidir. ANCAK endeks bir tamsayı ise yalnızca güncellenir.

Yani dizi seyrek. ANCAK, azaltma (), Math.max () ve "for ... of" gibi yerleşik işlevler, 0 formundaki olası tam sayı indekslerinin tüm aralığını uzunluğa kadar gezecek ve "tanımsız" döndüren birçok şeyi ziyaret edecek. ANCAK 'for ... in' döngüleri beklediğiniz gibi yapabilir, yalnızca tanımlanmış anahtarları ziyaret edebilir.

İşte Node.js kullanan bir örnek:

"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]}`);

veren:

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

Fakat. Henüz bahsedilmeyen Dizilerin olduğu daha fazla köşe vakası var.


2

Seyreklik (veya yoğunluk), standart olmayan process.memoryUsage () ile NodeJS için ampirik olarak doğrulanabilir .

Bazen düğüm, diziyi seyrek tutacak kadar akıllıdır:

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

Bazen düğüm onu ​​yoğunlaştırmayı seçer (bu davranış ileride optimize edilebilir):

> 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

Sonra tekrar seyrek:

> 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

Bu nedenle, belki de orijinal AIX çekirdek hatası hakkında fikir edinmek için yoğun bir dizi kullanmak, benzer bir aralıkla zorlanmak zorunda kalabilir :

> 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

Çünkü neden düşmesini sağlamıyorsun?

> 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
Güzel, ve on yıllık sorumun hala geçerli olmasına şaşırdım!
Berry

1

Olabilirler ama her zaman olmak zorunda değiller ve olmadıklarında daha iyi performans gösterebilirler.

Bir dizi örneğinde dizin seyrekliğinin nasıl test edileceğiyle ilgili bir tartışma: https://benmccormick.org/2018/06/19/code-golf-sparse-arrays/

Bu kod golf (en az karakter) kazananı:

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

Temel olarak, uzunluk değerini !!azaltırken ve yanlış / doğru sayısal sonucun sertleştirilmiş booleanını döndürürken dizine alınmış girişler için dizide gezinme (eğer toplayıcı tamamen sıfıra indirilirse, dizin tamamen doldurulur ve seyrek değildir). Charles Merriam'ın yukarıdaki uyarıları da dikkate alınmalıdır ve bu kod onlara hitap etmez, ancak var'ın arr[var]= (something)tamsayı olmadığı elemanlar atarken meydana gelebilecek hashing uygulanmış dize girişleri için geçerlidir .

Dizin seyrekliğini önemsemenin bir nedeni, performans üzerindeki etkileridir ve komut dosyası motorları arasında farklılık gösterebilir, burada dizi oluşturma / .initialization hakkında harika bir tartışma var: JavaScript bildirirken "Array ()" ve "[]" arasındaki fark nedir dizi?

Bu gönderiye yeni bir yanıt, V8'in dizileri seyreklik gibi özelliklerin test edilmesinden kaçınmak (yeniden) için etiketleyerek nasıl optimize etmeye çalıştığına dair bu derinlemesine bir bağlantı içeriyor: https://v8.dev/blog/elements-kinds . Blog yazısı Eylül 17'den kalmadır ve materyalde bazı değişiklikler söz konusudur, ancak günlük gelişim için çıkarımların dökümü faydalı ve açıktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.