Per dokumentasi Java, kode hash untuk String
objek dihitung sebagai:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
menggunakan
int
aritmatika, di manas[i]
adalah saya th karakter string,n
adalah panjang string, dan^
menunjukkan eksponensial.
Mengapa 31 digunakan sebagai pengganda?
Saya mengerti bahwa pengganda harus merupakan bilangan prima yang relatif besar. Jadi mengapa tidak 29, atau 37, atau bahkan 97?
Jawaban:
Menurut Joshua Bloch's Java Efektif (buku yang tidak cukup direkomendasikan, dan yang saya beli terima kasih untuk terus-menerus menyebutkan tentang stackoverflow):
(dari Bab 3, Butir 9: Selalu menimpa kode hash saat Anda menimpa sama dengan, halaman 48)
sumber
Seperti Goodrich dan Tamassia tunjukkan, Jika Anda mengambil lebih dari 50.000 kata bahasa Inggris (dibentuk sebagai gabungan dari daftar kata yang disediakan dalam dua varian Unix), menggunakan konstanta 31, 33, 37, 39, dan 41 akan menghasilkan kurang dari 7 tabrakan dalam setiap kasus. Mengetahui hal ini, seharusnya tidak mengejutkan bahwa banyak implementasi Java memilih salah satu dari konstanta ini.
Secara kebetulan, saya tengah membaca bagian "kode hash polinomial" ketika saya melihat pertanyaan ini.
EDIT: di sini ada tautan ke ~ 10mb buku PDF yang saya maksud di atas. Lihat bagian 10.2 Tabel hash (halaman 413) dari Struktur Data dan Algoritma di Jawa
sumber
Pada (kebanyakan) prosesor lama, mengalikannya dengan 31 bisa relatif murah. Pada ARM, misalnya, hanya satu instruksi:
Sebagian besar prosesor lain akan membutuhkan shift dan pengurangan instruksi yang terpisah. Namun, jika pengganda Anda lambat, ini masih merupakan kemenangan. Prosesor modern cenderung memiliki pengganda cepat sehingga tidak membuat banyak perbedaan, asalkan 32 berada di sisi yang benar.
Ini bukan algoritma hash yang hebat, tapi cukup bagus dan lebih baik daripada kode 1.0 (dan jauh lebih baik daripada spesifikasi 1.0!).
sumber
String.hashCode
mendahului StrongARM yang, IIRC, memperkenalkan pengali 8-bit dan mungkin meningkat menjadi dua siklus untuk aritmatika / logika gabungan dengan operasi shift.Map.Entry
telah diperbaiki secara spesifikkey.hashCode() ^ value.hashCode()
meskipun itu bukan pasangan yang tidak teratur, karenakey
danvalue
memiliki arti yang sama sekali berbeda. Ya, itu menyiratkan bahwaMap.of(42, 42).hashCode()
atauMap.of("foo", "foo", "bar", "bar").hashCode()
, dll, dapat diprediksi nol. Jadi jangan gunakan peta sebagai kunci untuk peta lain ...Dengan mengalikan, bit digeser ke kiri. Ini menggunakan lebih banyak ruang kode hash yang tersedia, mengurangi tabrakan.
Dengan tidak menggunakan kekuatan dua, bit yang lebih rendah, paling kanan diisi juga, untuk dicampur dengan potongan data berikutnya yang masuk ke hash.
Ekspresi
n * 31
setara dengan(n << 5) - n
.sumber
Anda dapat membaca alasan asli Bloch di bawah "Komentar" di http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622 . Dia menyelidiki kinerja fungsi hash yang berbeda sehubungan dengan "ukuran rantai rata-rata" yang dihasilkan dalam tabel hash.
P(31)
adalah salah satu fungsi umum selama waktu itu yang ia temukan dalam buku K&R (tetapi bahkan Kernighan dan Ritchie tidak dapat mengingat dari mana asalnya). Pada akhirnya dia pada dasarnya harus memilih satu dan jadi dia mengambilP(31)
karena tampaknya berkinerja cukup baik. MeskipunP(33)
tidak terlalu buruk dan perkalian dengan 33 sama cepat untuk dihitung (hanya bergeser 5 dan tambahan), ia memilih 31 karena 33 bukan yang utama:Jadi alasannya tidak rasional seperti banyak jawaban di sini tampaknya menyiratkan. Tetapi kita semua baik dalam memberikan alasan rasional setelah keputusan usus (dan bahkan Bloch mungkin cenderung untuk itu).
sumber
Sebenarnya, 37 akan bekerja dengan cukup baik! z: = 37 * x dapat dihitung sebagai
y := x + 8 * x; z := x + 4 * y
. Kedua langkah sesuai dengan satu instruksi LEA x86, jadi ini sangat cepat.Bahkan, perkalian dengan prime 73 yang bahkan lebih besar dapat dilakukan pada kecepatan yang sama dengan pengaturan
y := x + 8 * x; z := x + 8 * y
.Menggunakan 73 atau 37 (bukannya 31) mungkin lebih baik, karena mengarah ke kode yang lebih padat : Kedua instruksi LEA hanya mengambil 6 byte vs 7 byte untuk bergerak + shift + kurangi untuk perkalian dengan 31. Salah satu kemungkinan peringatan adalah bahwa 3-argumen instruksi LEA yang digunakan di sini menjadi lebih lambat pada arsitektur jembatan Sandy Intel, dengan latensi meningkat 3 siklus.
Selain itu, 73 adalah nomor favorit Sheldon Cooper.
sumber
Neil Coffey menjelaskan mengapa 31 digunakan dalam Ironing out the bias .
Pada dasarnya menggunakan 31 memberi Anda distribusi probabilitas set-bit yang lebih rata untuk fungsi hash.
sumber
Dari JDK-4045622 , di mana Joshua Bloch menjelaskan alasan mengapa
String.hashCode()
implementasi (baru) tertentu dipilihsumber
Bloch tidak cukup masuk ke ini, tetapi alasan saya selalu mendengar / percaya adalah bahwa ini adalah aljabar dasar. Hash bermuara pada operasi multiplikasi dan modulus, yang berarti bahwa Anda tidak pernah ingin menggunakan angka dengan faktor-faktor umum jika Anda dapat membantu. Dengan kata lain, bilangan yang relatif prima memberikan distribusi jawaban yang merata.
Angka-angka yang menggunakan hash biasanya:
Anda benar-benar hanya bisa mengendalikan beberapa dari nilai-nilai ini, jadi sedikit perhatian ekstra harus dilakukan.
sumber
Dalam versi terbaru JDK, 31 masih digunakan. https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/String.html#hashCode ()
Tujuan dari hash string adalah
^
dalam dokumen perhitungan kode hash, ini membantu unik)31 adalah nilai maks dapat dimasukkan ke dalam register 8 bit (= 1 byte), adalah bilangan prima terbesar yang dapat dimasukkan ke dalam register 1 byte, adalah angka ganjil.
Multiply 31 adalah << 5 lalu kurangi sendiri, oleh karena itu perlu sumber daya yang murah.
sumber
Saya tidak yakin, tapi saya kira mereka menguji beberapa sampel bilangan prima dan menemukan bahwa 31 memberikan distribusi terbaik atas beberapa sampel dari String yang mungkin.
sumber
Ini karena 31 memiliki properti yang bagus - perkaliannya dapat diganti dengan pergeseran bitwise yang lebih cepat daripada perkalian standar:
sumber