Gli array Javascript sono sparsi?


97

Cioè, se utilizzo l'ora corrente come indice nell'array:

array[Date.getTime()] = value;

l'interprete istanzerà tutti gli elementi da 0 a adesso? I diversi browser lo fanno in modo diverso?

Ricordo che c'era un bug nel kernel AIX , che creava pseudo-tty su richiesta, ma se si diceva "echo> / dev / pty10000000000" si creava / dev / pty0, / dev / pty1, .... e poi cadere morto. È stato divertente alle fiere, ma non voglio che questo accada ai miei clienti.


1
Un possibile svantaggio di questa operazione è la difficoltà di eseguire il debug in Firebug. un'istruzione di log sull'array elencherà solo i primi 1000 elementi dell'array, che saranno tutti "indefiniti". Inoltre, array.length ti dirà che il tuo array contiene n elementi, anche se n-1 sono solo valori indefiniti "fantasma".
Michael Butler

Il debug ora è OK in Chrome: ecco un esempio di output della console: [vuoto × 9564, Oggetto, vuoto × 105, Oggetto, vuoto × 10, Oggetto, vuoto × 12, Oggetto, vuoto × 9, Oggetto, vuoto × 21, Object, empty × 9, Object]
jsalvata

Risposte:


40

Il modo esatto in cui gli array JavaScript vengono implementati differisce da browser a browser, ma generalmente ricadono su un'implementazione sparsa - molto probabilmente la stessa usata per l'accesso alle proprietà di oggetti regolari - se l'uso di un array effettivo sarebbe inefficiente.

Dovrai chiedere a qualcuno con maggiori conoscenze su implementazioni specifiche di rispondere a cosa innesca esattamente il passaggio da denso a scarso, ma il tuo esempio dovrebbe essere perfettamente sicuro. Se vuoi ottenere un array denso, dovresti chiamare il costruttore con un argomento di lunghezza esplicito e sperare che ne avrai effettivamente uno.

Vedi questa risposta per una descrizione più dettagliata di olliej.


1
Non penso che tu abbia effettivamente un array denso se dici qualcosa di simile foo = new Array(10000). Tuttavia, questo dovrebbe funzionare: foo = Array.apply(null, {length: 10});.
doubleOrt

70

Sì. In realtà sono tabelle hash internamente, quindi puoi usare non solo interi grandi ma anche stringhe, float o altri oggetti. Tutte le chiavi vengono convertite in stringhe tramite toString()prima di essere aggiunte all'hash. Puoi confermarlo con un codice di prova:

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

Visualizza:

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

Nota come ho usato la for...insintassi, che ti dà solo gli indici che sono effettivamente definiti. Se si utilizza lo for (var i = 0; i < array.length; ++i)stile di iterazione più comune , ovviamente si avranno problemi con gli indici di array non standard.


9
la maggior parte delle implementazioni JS memorizza le proprietà indicizzate numericamente in un array effettivo, se possibile; questa è magia dietro le quinte, però: dal punto di vista del linguaggio, gli array sono oggetti regolari con una lengthproprietà magica
Christoph

7
@ John: lengthè invisibile solo nei for..inloop perché ha il DontEnumflag impostato; in ES5, l'attributo della proprietà viene chiamato enumerablee può essere impostato esplicitamente tramiteObject.defineProperty()
Christoph

14
Tutte le chiavi oggetto in JavaScript sono sempre String; qualsiasi altra cosa che metti nel pedice viene toString()-ed. Combina questo con l'imprecisione dell'intero di Large Number e significa che se imposti a[9999999999999999]=1, a[10000000000000000]sarà 1 (e molti altri comportamenti sorprendenti). L'uso di numeri non interi come chiavi non è saggio e gli oggetti arbitrari sono giusti.
bobince

72
Allora userai solo le stringhe come chiavi oggetto, né più né meno. String sarà il tipo che utilizzerai e il tipo di chiave sarà String. Intero non utilizzerai, né utilizzerai numeri non interi, eccetto che poi procederai a lanciare su String. Gli oggetti arbitrari sono giusti.
Crescent Fresh

8
Gli indici di matrice devono essere numeri interi. array [3.14] = pi funziona perché Array è ereditato da Object. Esempio: var x = []; x [.1] = 5; Allora x ha una lunghezza di 0 ancora.
Mike Blandford

10

Potresti evitare il problema utilizzando una sintassi javascript progettata per questo genere di cose. Puoi trattarlo come un dizionario, ma la sintassi "per ... in ..." ti permetterà di prenderli tutti.

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

7

Gli oggetti Javascript sono sparsi e gli array sono solo oggetti specializzati con una proprietà length mantenuta automaticamente (che in realtà è una più grande dell'indice più grande, non il numero di elementi definiti) e alcuni metodi aggiuntivi. Sei al sicuro in entrambi i casi; usa un array se hai bisogno delle sue funzionalità extra e un oggetto altrimenti.


4
questo da un punto di vista linguistico; le implementazioni utilizzano effettivamente array reali per memorizzare proprietà numeriche dense
Christoph il

6

La risposta, come di solito accade con JavaScript, è "è un po 'più strano ...."

L'utilizzo della memoria non è definito e qualsiasi implementazione può essere stupida. In teoria, const a = []; a[1000000]=0;potrebbe bruciare megabyte di memoria, come potrebbe const a = [];. In pratica, anche Microsoft evita queste implementazioni.

Justin Love sottolinea che l'attributo length è il set di indici più alto . MA è aggiornato solo se l'indice è un numero intero.

Quindi, la matrice è scarsa. MA funzioni incorporate come reduce (), Math.max () e "for ... of" percorreranno l'intera gamma di possibili indici interi da 0 alla lunghezza, visitando molti che restituiscono 'undefined'. MA i cicli 'for ... in' potrebbero fare come ti aspetti, visitando solo le chiavi definite.

Ecco un esempio utilizzando 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]}`);

dando:

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

Ma. Ci sono altri casi d'angolo con array non ancora menzionati.


2

La scarsità (o densità) può essere confermata empiricamente per NodeJS con process.memoryUsage () non standard .

A volte il nodo è abbastanza intelligente da mantenere l'array sparso:

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

A volte node sceglie di renderlo denso (questo comportamento potrebbe essere ottimizzato in futuro):

> 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

Quindi di nuovo sparsi:

> 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

Quindi forse utilizzando un array denso per avere un'idea del bug del kernel AIX originale potrebbe essere necessario forzare con un intervallo simile :

> 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

Perché perché non farlo cadere?

> 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
Bello, e sono un po 'stupito che la mia domanda di dieci anni sia ancora rilevante!
Berry

1

Possono esserlo, ma non sempre devono esserlo, e possono funzionare meglio quando non lo sono.

Ecco una discussione su come testare la scarsità dell'indice in un'istanza di array: https://benmccormick.org/2018/06/19/code-golf-sparse-arrays/

Questo codice vincitore di golf (minor numero di caratteri) è:

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

Fondamentalmente percorrendo l'array per le voci indicizzate mentre si decrementa il valore della lunghezza e si restituisce il !!booleano indurito del risultato numerico falso / vero (se l'accumulatore viene decrementato fino a zero, l'indice è completamente popolato e non sparso). Anche gli avvertimenti di Charles Merriam di cui sopra dovrebbero essere considerati e questo codice non li risolve, ma si applicano alle voci di stringhe con hash che possono verificarsi quando si assegnano elementi con arr[var]= (something)var non era un numero intero.

Il motivo per cui preoccuparsi della scarsità degli indici sono i suoi effetti sulle prestazioni, che possono differire tra i motori di script, c'è una grande discussione sulla creazione di array / .initializzazione qui: Qual è la differenza tra "Array ()" e "[]" durante la dichiarazione di un JavaScript Vettore?

Una risposta recente a quel post ha un collegamento a questa immersione profonda in come V8 cerca di ottimizzare gli array taggandoli per evitare (ri-) test per caratteristiche come la scarsità: https://v8.dev/blog/elements-kinds . Il post del blog è del settembre '17 e il materiale è soggetto ad alcune modifiche, ma la ripartizione delle implicazioni per lo sviluppo quotidiano è utile e chiara.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.