SparseArray vs HashMap


177

Posso pensare a diversi motivi per cui HashMaps con i tasti interi è molto meglio di SparseArrays:

  1. La documentazione di Android per un SparseArraydice "È generalmente più lento di un tradizionale HashMap".
  2. Se scrivi codice usando HashMaps anziché SparseArrays, il tuo codice funzionerà con altre implementazioni di Map e sarai in grado di utilizzare tutte le API Java progettate per Maps.
  3. Se scrivi codice usando HashMaps anziché SparseArrays il tuo codice funzionerà in progetti non Android.
  4. Mappa le sostituzioni equals()e hashCode(), mentre SparseArraynon lo fa.

Tuttavia, ogni volta che provo a usare un HashMaptasto con numeri interi in un progetto Android, IntelliJ mi dice che dovrei usare SparseArrayinvece un . Lo trovo davvero difficile da capire. Qualcuno sa qualche motivo convincente per l'utilizzo di SparseArrays?

Risposte:


235

SparseArraypuò essere usato per sostituire HashMapquando la chiave è di tipo primitivo. Esistono alcune varianti per diversi tipi di chiave / valore, anche se non tutte sono pubblicamente disponibili.

I vantaggi sono:

  • privo di allocazione
  • Niente boxe

svantaggi:

  • Generalmente più lento, non indicato per raccolte di grandi dimensioni
  • Non funzioneranno in un progetto non Android

HashMap può essere sostituito dal seguente:

SparseArray          <Integer, Object>
SparseBooleanArray   <Integer, Boolean>
SparseIntArray       <Integer, Integer>
SparseLongArray      <Integer, Long>
LongSparseArray      <Long, Object>
LongSparseLongArray  <Long, Long>   //this is not a public class                                 
                                    //but can be copied from  Android source code 

In termini di memoria, ecco un esempio di SparseIntArrayvs HashMap<Integer, Integer>per 1000 elementi:

SparseIntArray:

class SparseIntArray {
    int[] keys;
    int[] values;
    int size;
}

Classe = 12 + 3 * 4 = 24 byte
Matrice = 20 + 1000 * 4 = 4024 byte
Totale = 8.072 byte

HashMap:

class HashMap<K, V> {
    Entry<K, V>[] table;
    Entry<K, V> forNull;
    int size;
    int modCount;
    int threshold;
    Set<K> keys
    Set<Entry<K, V>> entries;
    Collection<V> values;
}

Classe = 12 + 8 * 4 = 48 byte
Voce = 32 + 16 + 16 = 64 byte
Matrice = 20 + 1000 * 64 = 64024 byte
Totale = 64.136 byte

Fonte: ricordi Android di Romain Guy dalla diapositiva 90.

I numeri sopra sono la quantità di memoria (in byte) allocata sull'heap da JVM. Possono variare in base alla specifica JVM utilizzata.

Il java.lang.instrumentpacchetto contiene alcuni metodi utili per operazioni avanzate come il controllo della dimensione di un oggetto con getObjectSize(Object objectToSize).

Ulteriori informazioni sono disponibili dalla documentazione ufficiale Oracle .

Classe = 12 byte + (n variabili di istanza) * 4 byte
Matrice = 20 byte + (n elementi) * (dimensione elemento)
Voce = 32 byte + (dimensione 1 ° elemento) + (dimensione 2 ° elemento)


15
Qualcuno può guidarmi da dove provengono quei "12 + 3 * 4" e "20 + 1000 * 4"?
Marian Paździoch,

5
@ MarianPaździoch, ha mostrato una presentazione ( speakerdeck.com/romainguy/android-memories ) in cui una classe occupa 12 byte + 3 variabili di 4 byte, un array (riferimento) occupa 20 byte (dlmalloc - 4, overhead dell'oggetto - 8, larghezza e riempimento - 8).
CoolMind

1
Per la cronaca, un altro svantaggio chiave di SparseArray è che come oggetto Android deve essere deriso per i test unitari. Ove possibile, ora uso gli oggetti Java per semplificare i test.
David G,

@DavidG Puoi semplicemente usare il plugin di smontaggio per deridere le dipendenze Android.
bufera di neve

1
Anche se non stai facendo Android, copiare la classe nel tuo progetto non è difficile, dipende solo da altre 3 classi. Licenza APL significa che va bene farlo, qualunque sia la licenza con cui stai lavorando.
Yann TM

35

Sono venuto qui volendo solo un esempio di come usare SparseArray. Questa è una risposta supplementare per questo.

Crea uno SparseArray

SparseArray<String> sparseArray = new SparseArray<>();

A SparseArraymappa numeri interi per alcuni Object, in modo da poter sostituire Stringnell'esempio sopra con qualsiasi altro Object. Se si stanno mappando numeri interi su numeri interi, utilizzare SparseIntArray.

Aggiungi o aggiorna elementi

Utilizzare put(o append) per aggiungere elementi all'array.

sparseArray.put(10, "horse");
sparseArray.put(3, "cow");
sparseArray.put(1, "camel");
sparseArray.put(99, "sheep");
sparseArray.put(30, "goat");
sparseArray.put(17, "pig");

Si noti che intnon è necessario che le chiavi siano in ordine. Questo può anche essere usato per cambiare il valore in una particolare intchiave.

Rimuovi elementi

Utilizzare remove(o delete) per rimuovere elementi dall'array.

sparseArray.remove(17); // "pig" removed

Il intparametro è la chiave intera.

Valori di ricerca per una chiave int

Utilizzare getper ottenere il valore per una chiave intera.

String someAnimal = sparseArray.get(99);  // "sheep"
String anotherAnimal = sparseArray.get(200); // null

È possibile utilizzare get(int key, E valueIfKeyNotFound)se si desidera evitare di ottenere nullchiavi mancanti.

Scorri gli oggetti

È possibile utilizzare keyAte valueAtalcuni indici per scorrere la raccolta perché SparseArraymantiene un indice separato distinto dalle intchiavi.

int size = sparseArray.size();
for (int i = 0; i < size; i++) {

    int key = sparseArray.keyAt(i);
    String value = sparseArray.valueAt(i);

    Log.i("TAG", "key: " + key + " value: " + value);
}

// key: 1 value: camel
// key: 3 value: cow
// key: 10 value: horse
// key: 30 value: goat
// key: 99 value: sheep

Si noti che le chiavi sono ordinate in ordine crescente, non nell'ordine in cui sono state aggiunte.


18

Tuttavia, ogni volta che provo ad usare una HashMap con chiavi intere in un progetto Android, intelliJ mi dice che dovrei usare invece SparseArray.

È solo un avvertimento da questa documentazione della sua matrice sparsa:

È pensato per essere più efficiente in termini di memoria rispetto all'uso di una HashMap per mappare numeri interi su oggetti

La SparseArrayè fatto per essere efficiente della memoria che utilizzare HashMap normale, che è non consentire più vuoti all'interno della matrice non come HashMap. Non c'è nulla di cui preoccuparsi, puoi usare la tradizionale HashMap se desideri non preoccuparti dell'allocazione di memoria sul dispositivo.


5
I punti sul risparmio di memoria sono ovviamente validi, ma non ho mai capito perché Android non avrebbe potuto fare in modo che SparseArray <T> implementasse Map <Integer, T> in modo da ottenere un'implementazione di Map efficiente in memoria - il migliore dei due mondi.
Paul Boddington,

3
@PaulBoddington ricorda anche che SparseArrayimpedisce all'intero chiave di essere la casella Auto, che è un'altra operazione e prestazioni in termini di costi. piuttosto che Mappa, inserirà automaticamente il numero intero primitivo inInteger
Rod_Algonquin,

Anche vero, ma se avessero sovraccaricato il metodo put includendone uno con firma put (int a, T t) saresti comunque in grado di inserire coppie chiave-valore nella mappa senza che le chiavi fossero auto-boxate. Penso solo che il Collections Framework sia così potente (uno dei migliori motivi per usare Java) che è una follia non approfittarne.
Paul Boddington,

6
Le raccolte @PaulBoddington si basano su oggetti non primitivi, quindi non funzioneranno all'interno dell'API Collezioni
Rod_Algonquin,

10

Una matrice sparsa in Java è una struttura di dati che mappa chiavi a valori. Stessa idea di una mappa, ma implementazione diversa:

  1. Una mappa è rappresentata internamente come una matrice di elenchi, in cui ogni elemento in questi elenchi è una coppia chiave, valore. Sia la chiave che il valore sono istanze di oggetto.

  2. Una matrice sparsa è semplicemente composta da due matrici: una matrice di chiavi (primitive) e una matrice di valori (oggetti). Possono esserci lacune in questi indici di matrici, da cui il termine "sparse" array.

L'interesse principale di SparseArray è che consente di risparmiare memoria utilizzando le primitive anziché gli oggetti come chiave.


10

Dopo aver cercato su Google, provo ad aggiungere alcune informazioni alle risposte già pubblicate:

Isaac Taylor ha effettuato un confronto delle prestazioni per SparseArrays e Hashmaps. Lo afferma

Hashmap e SparseArray sono molto simili per dimensioni della struttura dati inferiori a 1.000

e

quando la dimensione è stata aumentata a 10.000 [...] mark, Hashmap ha prestazioni maggiori con l'aggiunta di oggetti, mentre SparseArray ha prestazioni maggiori quando si recuperano oggetti. [...] A una dimensione di 100.000 [...] Hashmap perde prestazioni molto rapidamente

Un confronto su Edgblog mostra che uno SparseArray necessita di molta meno memoria di un HashMap a causa della chiave più piccola (int vs Integer) e del fatto che

un'istanza di HashMap.Entry deve tenere traccia dei riferimenti per la chiave, il valore e la voce successiva. Inoltre, deve anche memorizzare l'hash della voce come int.

In conclusione, direi che la differenza potrebbe importare se hai intenzione di memorizzare molti dati nella tua mappa. Altrimenti, basta ignorare l'avvertimento.


4

La documentazione Android per uno SparseArray dice "È generalmente più lento di un HashMap tradizionale".

Si, è giusto. Ma quando hai solo 10 o 20 articoli, la differenza di prestazioni dovrebbe essere insignificante.

Se scrivi codice utilizzando HashMaps anziché SparseArrays, il tuo codice funzionerà con altre implementazioni di Map e sarai in grado di utilizzare tutte le API Java progettate per Maps

Penso che il più delle volte usiamo solo HashMapper cercare un valore associato a una chiave mentre SparseArrayè davvero bravo in questo.

Se scrivi codice usando HashMaps anziché SparseArrays, il tuo codice funzionerà in progetti non Android.

Il codice sorgente di SparseArray è abbastanza semplice e facile da capire in modo da pagare solo pochi sforzi per spostarlo su altre piattaforme (attraverso una semplice COPIA e incolla).

Mappa sovrascrive equals () e hashCode () mentre SparseArray no

Tutto quello che posso dire è, (per la maggior parte degli sviluppatori), a chi importa?

Un altro aspetto importante SparseArrayè che utilizza solo un array per archiviare tutti gli elementi durante l' HashMaputilizzo Entry, quindi SparseArraycosta significativamente meno memoria di un HashMap, vedere questo


1

È un peccato che il compilatore emetta un avviso. Immagino che HashMap sia stato molto abusato per la memorizzazione di oggetti.

SparseArrays hanno il loro posto. Dato che usano un algoritmo di ricerca binaria per trovare un valore in un array, devi considerare cosa stai facendo. La ricerca binaria è O (log n) mentre la ricerca hash è O (1). Ciò non significa necessariamente che la ricerca binaria sia più lenta per un determinato set di dati. Tuttavia, con l'aumentare del numero di voci, la potenza della tabella hash prende il sopravvento. Da qui i commenti in cui un basso numero di voci può essere uguale e probabilmente migliore rispetto all'utilizzo di una HashMap.

Una HashMap è valida solo come l'hash e può anche essere influenzata dal fattore di carico (penso che nelle versioni successive ignorino il fattore di carico in modo che possa essere ottimizzato meglio). Hanno anche aggiunto un hash secondario per assicurarsi che l'hash sia buono. Anche il motivo per cui SparseArray funziona davvero bene per relativamente poche voci (<100).

Vorrei suggerire che se hai bisogno di una tabella hash e desideri un migliore utilizzo della memoria per numeri interi primitivi (senza boxing automatico), ecc., Prova trove. ( http://trove.starlight-systems.com - licenza LGPL). (Nessuna affiliazione con trove, proprio come la loro biblioteca)

Con l'edificio multi-dex semplificato non abbiamo nemmeno bisogno di riconfezionare trove per ciò di cui avete bisogno. (trove ha molte classi)

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.