Perché Java hashCode () in String usa 31 come moltiplicatore?


481

Secondo la documentazione Java, il codice hash per un Stringoggetto viene calcolato come:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

usando l' intaritmetica, dove s[i]è l' i- esimo carattere della stringa, nè la lunghezza della stringa e ^indica esponenziazione.

Perché 31 viene utilizzato come moltiplicatore?

Capisco che il moltiplicatore dovrebbe essere un numero primo relativamente grande. Quindi perché non 29, 37 o 97?


1
Confronta anche stackoverflow.com/questions/1835976/… - Penso che 31 sia una cattiva scelta se scrivi le tue funzioni hashCode.
Hans-Peter Störr,

6
Se fosse 29, o 37, o addirittura 97, ti chiederesti "perché non 31?"
Marchese di Lorne,

2
@EJP è importante conoscere il motivo alla base della scelta di un no. a meno che il numero non sia il risultato di un trucco di magia nera.
Dushyant Sabharwal,

C'è un post sul blog di @ peter-lawrey a riguardo qui: vanilla-java.github.io/2018/08/12/… e qui: vanilla-java.github.io/2018/08/15/…
Christophe Roussy

@DushyantSabharwal Il mio punto è che avrebbe potuto essere 29 o 37 o 97, o 41, o molti altri valori, senza fare molta differenza pratica. Ne usavamo 37 nel 1976.
Marchese di Lorne il

Risposte:


406

Secondo Effective Java di Joshua Bloch (un libro che non può essere abbastanza raccomandato e che ho acquistato grazie alle continue citazioni su StackOverflow):

È stato scelto il valore 31 perché è un numero primo dispari. Se fosse pari e la moltiplicazione traboccasse, le informazioni andrebbero perse, poiché la moltiplicazione per 2 equivale allo spostamento. Il vantaggio di usare un numero primo è meno chiaro, ma è tradizionale. Una bella proprietà di 31 è che la moltiplicazione può essere sostituito da uno spostamento e una sottrazione per migliorare le prestazioni: 31 * i == (i << 5) - i. Le VM moderne eseguono automaticamente questo tipo di ottimizzazione.

(dal Capitolo 3, Articolo 9: Sostituisci sempre l'hashcode quando sostituisci equivale, pagina 48)


346
Bene, tutti i numeri primi sono dispari, tranne 2. Basta dire.
Kip

38
Non credo che Bloch stia dicendo che è stato scelto perché era uno strano primo, ma perché era strano E perché era primo (E perché può essere facilmente ottimizzato in uno spostamento / sottrazione).
matt b

50
31 è stato scelto coz è uno strano primo ??? Non ha alcun senso - dico 31 è stato scelto perché ha dato la migliore distribuzione - controlla computinglife.wordpress.com/2008/11/20/…
computinglife

65
Penso che la scelta di 31 sia piuttosto sfortunata. Certo, potrebbe salvare alcuni cicli della CPU su macchine vecchie, ma hai già collisioni di hash su stringhe ascii brevi come "@ e #! O Ca e DB. Questo non accade se si sceglie, ad esempio, 1327144003, o su almeno 524287 che consente anche il bitshift: 524287 * i == i << 19 - I.
Hans-Peter Störr

15
@ Jason Vedere la mia risposta stackoverflow.com/questions/1835976/... . Il mio punto è: si ottengono molte meno collisioni se si utilizza un numero primo più grande e non si perde nulla in questi giorni. Il problema è peggiore se si utilizzano lingue non inglesi con caratteri non ascii comuni. E 31 è stato un cattivo esempio per molti programmatori quando scrivevano le proprie funzioni hashCode.
Hans-Peter Störr,

80

Come sottolineano Goodrich e Tamassia , se si prendono più di 50.000 parole inglesi (formate come l'unione delle liste di parole fornite in due varianti di Unix), l'uso delle costanti 31, 33, 37, 39 e 41 produrrà meno di 7 collisioni in ogni caso. Sapendo questo, non dovrebbe sorprendere che molte implementazioni Java scelgano una di queste costanti.

Per coincidenza, ero nel bel mezzo della lettura della sezione "Codici hash polinomiali" quando ho visto questa domanda.

EDIT: ecco il link al libro PDF ~ 10mb di cui mi riferisco sopra. Vedere la sezione 10.2 Tabelle hash (pagina 413) di Strutture dati e algoritmi in Java


6
Si noti tuttavia che si potrebbero ottenere MODALITÀ più collisioni se si utilizza qualsiasi tipo di set di caratteri internazionale con caratteri comuni al di fuori dell'intervallo ASCII. Almeno, ho controllato questo per 31 e tedesco. Quindi penso che la scelta di 31 sia rotta.
Hans-Peter Störr,

1
@jJack, il link fornito nella tua risposta è interrotto.
SK Venkat,

Entrambi i collegamenti in questa risposta sono interrotti. Inoltre, l'argomento del primo paragrafo è in qualche modo incompleto; come si confrontano gli altri numeri dispari con i cinque elencati in questo benchmark?
Mark Amery, il

58

Su (principalmente) vecchi processori, moltiplicare per 31 può essere relativamente economico. Su un ARM, ad esempio, è solo un'istruzione:

RSB       r1, r0, r0, ASL #5    ; r1 := - r0 + (r0<<5)

La maggior parte degli altri processori richiederebbe un turno separato e sottrarre istruzioni. Tuttavia, se il moltiplicatore è lento, questa è comunque una vittoria. I processori moderni tendono ad avere moltiplicatori veloci, quindi non fa molta differenza, fintanto che 32 va dalla parte giusta.

Non è un ottimo algoritmo di hash, ma è abbastanza buono e migliore del codice 1.0 (e molto meglio delle specifiche 1.0!).


7
Abbastanza divertente, la moltiplicazione con 31 è sulla mia macchina desktop in realtà un po 'più lenta della moltiplicazione con, diciamo, 92821. Immagino che il compilatore cerchi di "ottimizzarlo" in shift e anche di aggiungere. :-)
Hans-Peter Störr,

1
Non credo di aver mai usato un ARM che non fosse altrettanto veloce con tutti i valori nell'intervallo +/- 255. L'uso di una potenza di 2 meno uno ha l'effetto sfortunato che una modifica corrispondente a due valori modifica il codice hash di una potenza di due. Un valore di -31 sarebbe stato migliore e penso che qualcosa come -83 (64 + 16 + 2 + 1) avrebbe potuto essere ancora migliore (miscelare i bit un po 'meglio).
supercat

@supercat Non convinto dal meno. Sembra che tornerai indietro verso gli zeri. / String.hashCodeprecede il StrongARM che, IIRC, ha introdotto un moltiplicatore di 8 bit e probabilmente aumentato a due cicli per l'aritmetica / logica combinata con le operazioni di spostamento.
Tom Hawtin: affronta il

1
@ TomHawtin-tackline: usando 31, l'hash di quattro valori sarebbe 29791 * a + 961 * b + 31 * c + d; usando -31, sarebbe -29791 * a + 961 * b - 31 * c + d. Non penso che la differenza sarebbe significativa se i quattro oggetti fossero indipendenti, ma se le coppie di oggetti adiacenti corrispondono, il codice hash risultante sarà il contributo di tutti gli oggetti non accoppiati, più alcuni multipli di 32 (da quelli accoppiati). Per le stringhe potrebbe non importare troppo, ma se si sta scrivendo un metodo generico per aggregazioni di hashing, la situazione in cui gli oggetti adiacenti corrispondono sarà sproporzionatamente comune.
supercat

3
@supercat fatto divertente, il codice hash di Map.Entryè stato corretto dalle specifiche per essere key.hashCode() ^ value.hashCode()nonostante non sia nemmeno una coppia non ordinata, poiché keye valueha un significato completamente diverso. Sì, ciò implica che Map.of(42, 42).hashCode()o Map.of("foo", "foo", "bar", "bar").hashCode(), ecc., Sono prevedibilmente zero. Quindi non usare le mappe come chiavi per altre mappe ...
Holger,

33

Moltiplicando, i bit vengono spostati a sinistra. Questo utilizza più spazio disponibile dei codici hash, riducendo le collisioni.

Non usando una potenza di due, vengono popolati anche i bit più in basso di ordine inferiore, da mescolare con il prossimo pezzo di dati che va nell'hash.

L'espressione n * 31è equivalente a (n << 5) - n.


29

Puoi leggere il ragionamento originale di Bloch alla voce "Commenti" in http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622 . Ha studiato le prestazioni di diverse funzioni hash in relazione alla "dimensione media della catena" risultante in una tabella hash. P(31)era una delle funzioni comuni in quel periodo che trovò nel libro di K&R (ma nemmeno Kernighan e Ritchie non ricordavano da dove provenisse). Alla fine, in pratica, ha dovuto sceglierne uno e così ha preso P(31)dal momento che sembrava funzionare abbastanza bene. Anche se P(33)non era davvero peggio e la moltiplicazione per 33 è ugualmente veloce da calcolare (solo uno spostamento di 5 e un'aggiunta), ha optato per 31 poiché 33 non è un numero primo:

Dei restanti quattro, probabilmente selezionerei P (31), poiché è il più economico da calcolare su una macchina RISC (perché 31 è la differenza di due potenze di due). P (33) è altrettanto economico da calcolare, ma le sue prestazioni sono leggermente peggiori e 33 è composito, il che mi rende un po 'nervoso.

Quindi il ragionamento non è stato così razionale come molte delle risposte qui sembrano implicare. Ma siamo tutti bravi a trovare ragioni razionali dopo le decisioni dell'intestino (e anche Bloch potrebbe essere incline a questo).


2
Una ricerca approfondita e una risposta imparziale!
Vishal K,

22

In realtà, 37 funzionerebbe abbastanza bene! z: = 37 * x può essere calcolato come y := x + 8 * x; z := x + 4 * y. Entrambi i passaggi corrispondono a una delle istruzioni LEA x86, quindi questo è estremamente veloce.

In effetti, la moltiplicazione con il primo ancora più grande 73 potrebbe essere effettuata alla stessa velocità impostando y := x + 8 * x; z := x + 8 * y.

L'uso di 73 o 37 (anziché 31) potrebbe essere migliore, perché porta a un codice più denso : le due istruzioni LEA richiedono solo 6 byte rispetto ai 7 byte per spostamento + spostamento + sottrazione per la moltiplicazione per 31. Un possibile avvertimento è che le istruzioni LEA a 3 argomenti utilizzate qui sono diventate più lente sull'architettura Intel Sandy Bridge, con una latenza aumentata di 3 cicli.

Inoltre, 73 è il numero preferito di Sheldon Cooper.


5
Sei un programmatore pasquale o qualcosa del genere? cosa c'è con: = roba?
Mainguy,

11
@Mainguy In realtà è una sintassi ALGOL ed è usata abbastanza spesso in pseudo-codice.
Approaching Darkness

4
ma nell'assemblaggio ARM la moltiplicazione per 31 può essere eseguita in una sola istruzione
phuclv,


In TPOP (1999) si può leggere dei primi Java (p.57): "... Il problema è stato risolto sostituendo l'hash con uno equivalente a quello che abbiamo mostrato (con un moltiplicatore di 37 ) ..."
Miku,

19

Neil Coffey spiega perché 31 viene utilizzato in Stiratura del pregiudizio .

Fondamentalmente l'uso di 31 ti dà una distribuzione di probabilità più omogenea per la funzione hash.


12

Da JDK-4045622 , in cui Joshua Bloch descrive i motivi per cui è String.hashCode()stata scelta quella particolare (nuova) implementazione

La tabella seguente riassume le prestazioni delle varie funzioni hash descritte sopra, per tre set di dati:

1) Tutte le parole e le frasi con le voci nel 2 ° Dizionario integrale internazionale di Merriam-Webster (311.141 stringhe, lunghezza media 10 caratteri).

2) Tutte le stringhe in / bin / , / usr / bin / , / usr / lib / , / usr / ucb / e / usr / openwin / bin / * (66.304 stringhe, lunghezza media di 21 caratteri).

3) Un elenco di URL raccolti da un web crawler che ha funzionato per diverse ore ieri sera (28.372 stringhe, lunghezza media 49 caratteri).

La metrica delle prestazioni mostrata nella tabella è la "dimensione media della catena" su tutti gli elementi nella tabella hash (ovvero, il valore atteso del numero di chiave viene confrontato per cercare un elemento).

                          Webster's   Code Strings    URLs
                          ---------   ------------    ----
Current Java Fn.          1.2509      1.2738          13.2560
P(37)    [Java]           1.2508      1.2481          1.2454
P(65599) [Aho et al]      1.2490      1.2510          1.2450
P(31)    [K+R]            1.2500      1.2488          1.2425
P(33)    [Torek]          1.2500      1.2500          1.2453
Vo's Fn                   1.2487      1.2471          1.2462
WAIS Fn                   1.2497      1.2519          1.2452
Weinberger's Fn(MatPak)   6.5169      7.2142          30.6864
Weinberger's Fn(24)       1.3222      1.2791          1.9732
Weinberger's Fn(28)       1.2530      1.2506          1.2439

Guardando questa tabella, è chiaro che tutte le funzioni ad eccezione della funzione Java corrente e le due versioni rotte della funzione di Weinberger offrono prestazioni eccellenti, quasi indistinguibili. Immagino fortemente che questa prestazione sia essenzialmente l '"ideale teorico", che è quello che otterresti se tu usassi un vero generatore di numeri casuali al posto di una funzione hash.

Escluderei la funzione WAIS poiché le sue specifiche contengono pagine di numeri casuali e le sue prestazioni non sono migliori di nessuna delle funzioni molto più semplici. Qualsiasi delle restanti sei funzioni sembrano scelte eccellenti, ma dobbiamo sceglierne una. Suppongo che escluderei la variante di Vo e la funzione di Weinberger a causa della loro maggiore complessità, sebbene minore. Dei restanti quattro, probabilmente selezionerei P (31), poiché è il più economico da calcolare su una macchina RISC (perché 31 è la differenza di due potenze di due). P (33) è altrettanto economico da calcolare, ma le sue prestazioni sono leggermente peggiori e 33 è composito, il che mi rende un po 'nervoso.

Josh


5

Bloch non si occupa di questo, ma la logica che ho sempre sentito / creduto è che questa è algebra di base. Gli hash si riducono alle operazioni di moltiplicazione e modulo, il che significa che non puoi mai usare numeri con fattori comuni se puoi aiutarli. In altre parole, i numeri relativamente primi forniscono una distribuzione uniforme delle risposte.

I numeri che compongono usando un hash sono in genere:

  • modulo del tipo di dati in cui lo si inserisce (2 ^ 32 o 2 ^ 64)
  • modulo del conteggio dei bucket nella tabella hash (varia. In java era prima, ora 2 ^ n)
  • moltiplica o sposta per un numero magico nella tua funzione di missaggio
  • Il valore di input

Puoi davvero controllare solo un paio di questi valori, quindi è necessaria una piccola attenzione in più.


4

Nell'ultima versione di JDK, 31 è ancora utilizzato. https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/String.html#hashCode ()

Lo scopo della stringa hash è

  • unico (vedere operatore ^ nel documento di calcolo hashcode, aiuta unico)
  • costo economico per il calcolo

31 è il valore massimo che può essere inserito nel registro a 8 bit (= 1 byte), il numero più grande può essere inserito nel registro a 1 byte, è il numero dispari.

Moltiplica 31 è << 5 quindi sottrae se stessa, quindi ha bisogno di risorse economiche.


3

Non sono sicuro, ma immagino che abbiano testato alcuni campioni di numeri primi e abbiano scoperto che 31 ha dato la migliore distribuzione su alcuni campioni di possibili stringhe.


1

Questo perché 31 ha una bella proprietà: la sua moltiplicazione può essere sostituita da uno spostamento bit a bit più veloce della moltiplicazione standard:

31 * i == (i << 5) - i
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.