Controllo dell'esistenza chiave in HashMap


309

Il controllo dell'esistenza delle chiavi in ​​HashMap è sempre necessario?

Ho una HashMap con diciamo 1000 voci e sto cercando di migliorare l'efficienza. Se si accede a HashMap molto frequentemente, il controllo dell'esistenza della chiave ad ogni accesso comporterà un notevole sovraccarico. Invece se la chiave non è presente e quindi si verifica un'eccezione, posso rilevare l'eccezione. (quando so che questo accadrà raramente). Ciò ridurrà della metà gli accessi a HashMap.

Questa potrebbe non essere una buona pratica di programmazione, ma mi aiuterà a ridurre il numero di accessi. O mi sto perdendo qualcosa qui?

[ Aggiornamento ] Non ho valori nulli in HashMap.


8
"Quindi si verifica un'eccezione" - quale eccezione? Questo non verrà da java.util.HashMap ...
serg10

Risposte:


513

Memorizzi mai un valore nullo? In caso contrario, puoi semplicemente fare:

Foo value = map.get(key);
if (value != null) {
    ...
} else {
    // No such key
}

In caso contrario, si potrebbe basta controllare per l'esistenza, se si ottiene un valore nullo restituito:

Foo value = map.get(key);
if (value != null) {
    ...
} else {
    // Key might be present...
    if (map.containsKey(key)) {
       // Okay, there's a key but the value is null
    } else {
       // Definitely no such key
    }
}

1
@Samuel: solo quando null è un valore possibile. Se sicuramente non hai valori nulli nella mappa, getva bene ed evita di fare due ricerche quando hai bisogno anche del valore.
Jon Skeet,

Sebbene questo sia probabilmente più chiaro come esempio, potresti anche scrivere if(value!=null || map.containsKey(key))per la seconda parte. Almeno se vuoi fare la stessa cosa in entrambi i modi - nessun codice ripetuto. Funzionerà a causa di corto circuito .
Cullub,

66

Non otterrai nulla controllando che la chiave esista. Questo è il codice di HashMap:

@Override
public boolean containsKey(Object key) {
    Entry<K, V> m = getEntry(key);
    return m != null;
}

@Override
public V get(Object key) {
    Entry<K, V> m = getEntry(key);
    if (m != null) {
        return m.value;
    }
    return null;
}

Controlla se il valore di ritorno get()è diverso da null.

Questo è il codice sorgente di HashMap.


Risorse:


2
Qual è il punto nel mostrare una specifica implementazione di questi metodi?
jarnbjo,

2
Per spiegarlo, nella maggior parte dei casi, la verifica dell'esistenza della chiave richiederà circa lo stesso tempo rispetto all'ottenimento del valore. Quindi non ottimizzerà nulla per verificare che la chiave esista effettivamente prima di ottenere il valore. So che è una generalizzazione, ma può aiutare a capire.
Colin Hebert,

Un buon collegamento è grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… (OpenJDK è fortemente derivato dal codice Sun) e sembra che mi sbagli. Stavo confrontando la versione per Java5 con Java6; funzionano diversamente in quest'area (ma entrambi sono corretti, così come i frammenti che hai pubblicato).
Donal Fellows,

2
Come sottolineato nella risposta accettata, questo serpente è chiaramente sbagliato. Ovviamente guadagni qualcosa controllando l'esistenza delle chiavi confrontando i valori: puoi differenziare le chiavi inesistenti dalle chiavi esistenti ma che sono mappate su null come valore.
Johannes H.

43

Il modo migliore è usare il containsKeymetodo di HashMap. Domani qualcuno aggiungerà null alla Mappa. È necessario distinguere tra presenza chiave e chiave con valore nullo.


Si. In alternativa, eseguire la sottoclasse di HashMap per impedire la memorizzazione completa null.
RobAu,

1
1+ per i tipi primitivi in ​​quanto non è richiesto un cast di valore non necessario utilizzando questa risposta
Prashant Bhanarkar,

È anche più fluente scrivere .containsKey () che controllare null. Dovremmo preoccuparci più dell'essere di facile lettura, che consente di risparmiare tempo agli sviluppatori, che di alcune ottimizzazioni minori, nella maggior parte dei casi. Almeno non ottimizzare prima che diventi necessario.
Max

23

Vuoi dire che hai un codice simile

if(map.containsKey(key)) doSomethingWith(map.get(key))

dappertutto? Quindi dovresti semplicemente controllare se map.get(key)restituito null e basta. A proposito, HashMap non genera eccezioni per le chiavi mancanti, ma restituisce null. L'unico caso in cui containsKeyè necessario è quando si memorizzano valori null, per distinguere tra un valore null e un valore mancante, ma questo è generalmente considerato una cattiva pratica.


8

Usa solo containsKey()per chiarezza. È veloce e mantiene il codice pulito e leggibile. Il punto centrale di HashMaps è che la ricerca dei tasti è veloce, assicurati solo che hashCode()e equals()siano implementati correttamente.


4
if(map.get(key) != null || (map.get(key) == null && map.containsKey(key)))

3

Puoi anche usare il computeIfAbsent()metodo nella HashMapclasse.

Nel seguente esempio, mapmemorizza un elenco di transazioni (numeri interi) che vengono applicati alla chiave (il nome del conto bancario). Per aggiungere 2 transazioni di 100e 200per checking_accountte puoi scrivere:

HashMap<String, ArrayList<Integer>> map = new HashMap<>();
map.computeIfAbsent("checking_account", key -> new ArrayList<>())
   .add(100)
   .add(200);

In questo modo non è necessario verificare se la chiave checking_accountesiste o meno.

  • Se non esiste, ne verrà creato uno e restituito dall'espressione lambda.
  • Se esiste, verrà restituito il valore per la chiave computeIfAbsent().

Davvero elegante! 👍


0

Di solito uso il linguaggio

Object value = map.get(key);
if (value == null) {
    value = createValue(key);
    map.put(key, value);
}

Ciò significa che colpisci la mappa solo due volte se manca la chiave


0
  1. Se la classe chiave è tua, assicurati che siano implementati i metodi hashCode () e equals ().
  2. Fondamentalmente l'accesso a HashMap dovrebbe essere O (1) ma con un'implementazione errata del metodo hashCode è diventato O (n), perché il valore con la stessa chiave hash verrà archiviato come Elenco collegato.

0

La risposta di Jon Skeet affronta bene i due scenari (mappa con nullvalore e non nullvalore) in modo efficiente.

Per quanto riguarda le voci numeriche e il problema dell'efficienza, vorrei aggiungere qualcosa.

Ho una HashMap con diciamo 1.000 voci e sto cercando di migliorare l'efficienza. Se si accede a HashMap molto frequentemente, la verifica dell'esistenza della chiave ad ogni accesso comporterà un notevole sovraccarico.

Una mappa con 1.000 voci non è una mappa enorme.
Oltre a una mappa con 5.000 o 10.000 voci.
Mapsono progettati per un rapido recupero con tali dimensioni.

Ora, si presume che hashCode()delle chiavi della mappa fornisca una buona distribuzione.

Se puoi usare un Integertipo di chiave, fallo.
Il suo hashCode()metodo è molto efficiente poiché le collisioni non sono possibili per intvalori univoci :

public final class Integer extends Number implements Comparable<Integer> {
    ...
    @Override
    public int hashCode() {
        return Integer.hashCode(value);
    }

    public static int hashCode(int value) {
        return value;
    }
    ...
}

Se per la chiave, è necessario utilizzare un altro tipo incorporato come Stringad esempio in cui viene spesso utilizzato Map, è possibile che si verifichino delle collisioni, ma da 1 000 a alcune migliaia di oggetti nel Map, si dovrebbe avere pochissimi come String.hashCode()metodo fornisce una buona distribuzione.

Se si utilizza un tipo personalizzato, eseguire l'override hashCode()e equals()correttamente e assicurarsi nel complesso che hashCode()fornisca una distribuzione equa.
È possibile fare riferimento all'articolo 9 di Java Effectiveesso si riferisce.
Ecco un post che descrive in modo dettagliato.

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.