2012-04-02 18 views
5

Alcuni principali classi JVM (come ad esempio String o List implementations) implementano equivale restituendo Σ 31^n * field_n.hashCode() per ogni field_n che è rilevante per il metodo equals. Inoltre, questo approccio è raccomandato da Joshua Bloch in Java efficace (articolo 9).hashCode strategie di attuazione

Tuttavia, altre classi come Map.Entry implementations seguono regole diverse. Ad esempio, la documentazione Map.Entry afferma che il codice hash di un Map.Entry dovrebbe essere

(e.getKey()==null ? 0 : e.getKey().hashCode())^
(e.getValue()==null ? 0 : e.getValue().hashCode()) 

Questo a volte può essere poco pratico da utilizzare in tabelle hash, poiché:

  • il codice hash di tutte le voci che hanno la stessa chiave e il valore è 0,
  • due voci e1 ed e2 in modo che e1.key = e2.value e e1.value = e2.key abbiano lo stesso codice hash.

Perché Java ha scelto questa specifica di implementazione per Map.Entry hashCode anziché, ad esempio, 31 * (e.getKey()==null ? 0 : e.getKey().hashCode()) + (e.getValue()==null ? 0 : e.getValue().hashCode())?

Edit 1:

Per aiutare a capire il problema, ecco un esempio di codice utile in cui il risultato ha prestazioni molto povera a causa di collisioni hash se molte voci hanno lo stesso valore chiave e.

Questo metodo calcola le frequenze delle voci di diverse mappe (utilizzando il Multiset di Guava).

public static <K, V> Multiset<Map.Entry<K, V>> computeEntryCounts(
     Iterable<Map<K, V>> maps) { 
    ImmutableMultiset.Builder<Map.Entry<K, V>> result = ImmutableMultiset.builder(); 
    for (Map<K, V> map : maps) { 
     for (Map.Entry<K, V> entry : map.entrySet()) { 
      result.add(entry); 
     } 
    } 
    return result.build(); 
} 
+0

L'implementazione non ha alcun effetto sull'output perché la chiave e il valore sono ** null **. Inoltre, non ci sono più di ** una ** voce con una chiave in una mappa in Java, una chiave si verifica solo ** una volta ** in qualsiasi implementazione della mappa. –

+0

Lo so, stavo dando per scontato che le chiavi di HashMap siano istanze di Map.Entry. Ciò può accadere se si desidera calcolare il conteggio totale per ciascuna voce di valori-chiave su più mappe. – jpountz

+0

Non riesco a vedere come questo influenzerà questo caso, a meno che non si desideri posizionare tutte le Map.Entry da tutte le mappe all'interno di una singola mappa per contarle, ma ciò sarebbe chiaramente sbagliato. –

risposta

4

Dubito che ci sia una buona ragione — Penso che sia solo una svista — ma non è un problema serio. Si presenterebbe solo se si disponesse di uno HashSet<Map.Entry<T,T>> o di uno HashMap<Map.Entry<T,T>,V>, cosa che non viene comunemente eseguita. (A cura di aggiungere: Oppure, come sottolinea Joachim Sauer qui di seguito, un HashSet<Map<T,T>> o un HashMap<Map<T,T>,V> —, inoltre, non comunemente fatto.)

Nota che HashMap<K,V> fa non uso Map.Entry<K,V>.hashCode(), perché sembra le voci dai loro chiavi solo, quindi utilizza solo K.hashCode().

+1

Poiché 'Map.hashCode()' è definito in termini di 'hashCode' dei suoi elementi' Map.Entry', questo appare anche in una 'Mappa , V2>'. O un 'Set >'. Ma anche quelli possono essere facilmente evitati (e di solito dovrebbero essere evitati per motivi di leggibilità e design). –

+0

@JoachimSauer: buon punto, grazie! – ruakh

+0

Tutto ciò che serve per imbattersi in questo problema è 'someHashSet.addAll (someMap.entrySet())' o qualcosa del genere [Problema di Guava] (http://code.google.com/p/guava-libraries/issues/detail ? id = 458). Tutte le voci come "a" => "a" hanno la garanzia di scontrarsi e le prestazioni sono garantite come terribili (o almeno meno ottimali dal JDK 8). Raramente ci si imbatte in esso, ma quando lo fai è piuttosto brutto. – maaartinus

0

La mia ipotesi personale è che hashCode dovrebbe essere veloce anche.

Poiché è possibile eseguire l'override di hashCode, non c'è alcun problema. cambiarlo quando si conosce un algoritmo migliore adatta per il vostro caso

+2

Siamo spiacenti, ma non è giusto. 'Map.Entry' è un'interfaccia * *. Non descrive un algoritmo * predefinito * che * it * implementa ma che le sottoclassi possono sovrascrivere; piuttosto, sta prescrivendo un algoritmo specifico che gli implementatori devono implementare. – ruakh

+0

Quando il sole/l'oracolo ha bisogno in tutti i casi di una certa implementazione, DEVONO usare una classe astratta invece di un'interfaccia. –