Saya ingin membuat HashMap yang besar tetapi put()
kinerjanya tidak cukup baik. Ada ide?
Saran struktur data lainnya diterima, tetapi saya memerlukan fitur pencarian dari Peta Java:
map.get(key)
Dalam kasus saya, saya ingin membuat peta dengan 26 juta entri. Menggunakan Java HashMap standar, put rate menjadi sangat lambat setelah 2-3 juta penyisipan.
Juga, apakah ada yang tahu jika menggunakan distribusi kode hash yang berbeda untuk kunci dapat membantu?
Metode kode hash saya:
byte[] a = new byte[2];
byte[] b = new byte[3];
...
public int hashCode() {
int hash = 503;
hash = hash * 5381 + (a[0] + a[1]);
hash = hash * 5381 + (b[0] + b[1] + b[2]);
return hash;
}
Saya menggunakan properti asosiatif tambahan untuk memastikan bahwa objek yang sama memiliki kode hash yang sama. Array adalah byte dengan nilai dalam kisaran 0 - 51. Nilai hanya digunakan sekali dalam salah satu array. Objeknya sama jika array a berisi nilai yang sama (dalam urutan apa pun) dan hal yang sama berlaku untuk array b. Jadi a = {0,1} b = {45,12,33} dan a = {1,0} b = {33,45,12} adalah sama.
EDIT, beberapa catatan:
Beberapa orang mengkritik penggunaan peta hash atau struktur data lain untuk menyimpan 26 juta entri. Saya tidak mengerti mengapa ini tampak aneh. Sepertinya masalah struktur data dan algoritme klasik bagi saya. Saya memiliki 26 juta item dan saya ingin dapat dengan cepat memasukkannya ke dalam dan mencarinya dari struktur data: beri saya struktur data dan algoritme.
Menetapkan kapasitas awal Java HashMap default menjadi 26 juta menurunkan kinerja.
Beberapa orang menyarankan untuk menggunakan database, dalam beberapa situasi lain yang pasti merupakan pilihan cerdas. Tapi saya benar-benar mengajukan pertanyaan tentang struktur data dan algoritme, database lengkap akan berlebihan dan jauh lebih lambat daripada solusi struktur data yang baik (setelah semua database hanyalah perangkat lunak tetapi akan memiliki komunikasi dan mungkin overhead disk).
sumber
Jawaban:
Seperti yang ditunjukkan banyak orang
hashCode()
metode yang harus disalahkan. Itu hanya menghasilkan sekitar 20.000 kode untuk 26 juta objek berbeda. Artinya rata-rata 1.300 objek per ember hash = sangat sangat buruk. Namun jika saya mengubah dua array menjadi angka di basis 52, saya dijamin mendapatkan kode hash unik untuk setiap objek:Array diurutkan untuk memastikan metode ini memenuhi
hashCode()
kontrak bahwa objek yang sama memiliki kode hash yang sama. Dengan menggunakan metode lama, jumlah rata-rata put per detik di atas 100.000 put, 100.000 hingga 2.000.000 adalah:Menggunakan metode baru memberikan:
Jauh lebih baik. Metode lama mundur dengan sangat cepat sementara yang baru mempertahankan hasil yang baik.
sumber
hashCode
metode ini. Menurut konvensi,hashCode
tidak mengubah status objek. Mungkin konstruktor akan menjadi tempat yang lebih baik untuk menyortirnya.int result = a[0]; result = result * 52 + a[1]; //etc
.hashCode()
agar berfungsi.Satu hal yang saya perhatikan dalam
hashCode()
metode Anda adalah bahwa urutan elemen dalam arraya[]
danb[]
tidak penting. Dengan demikian(a[]={1,2,3}, b[]={99,100})
akan memiliki nilai yang sama dengan(a[]={3,1,2}, b[]={100,99})
. Sebenarnya semua kuncik1
dan dik2
manasum(k1.a)==sum(k2.a)
dansum(k1.b)=sum(k2.b)
akan mengakibatkan benturan. Saya sarankan untuk memberi bobot pada setiap posisi array:di mana,
c0
,c1
danc3
yang berbeda konstanta (Anda dapat menggunakan konstanta yang berbeda untukb
jika perlu). Itu akan meratakan hal-hal sedikit lebih banyak.sumber
Untuk menguraikan Pascal: Apakah Anda memahami cara kerja HashMap? Anda memiliki beberapa slot di tabel hash Anda. Nilai hash untuk setiap kunci ditemukan, dan kemudian dipetakan ke entri dalam tabel. Jika dua nilai hash dipetakan ke entri yang sama - "benturan hash" - HashMap membuat daftar tertaut.
Tabrakan hash dapat mematikan kinerja peta hash. Dalam kasus ekstrim, jika semua kunci Anda memiliki kode hash yang sama, atau jika mereka memiliki kode hash yang berbeda tetapi semuanya dipetakan ke slot yang sama, maka peta hash Anda berubah menjadi daftar tertaut.
Jadi jika Anda melihat masalah kinerja, hal pertama yang akan saya periksa adalah: Apakah saya mendapatkan distribusi kode hash yang tampak acak? Jika tidak, Anda membutuhkan fungsi hash yang lebih baik. Nah, "lebih baik" dalam hal ini mungkin berarti "lebih baik untuk kumpulan data saya". Misalnya, Anda mengerjakan string, dan Anda mengambil panjang string untuk nilai hash. (Bukan cara kerja String.hashCode Java, tetapi saya hanya membuat contoh sederhana.) Jika string Anda memiliki panjang yang sangat bervariasi, dari 1 hingga 10.000, dan didistribusikan secara merata di seluruh rentang itu, ini bisa menjadi sangat bagus fungsi hash. Tetapi jika semua string Anda terdiri dari 1 atau 2 karakter, ini akan menjadi fungsi hash yang sangat buruk.
Edit: Saya harus menambahkan: Setiap kali Anda menambahkan entri baru, HashMap memeriksa apakah ini duplikat. Saat terjadi benturan hash, kunci masuk harus dibandingkan dengan setiap kunci yang dipetakan ke slot itu. Jadi dalam kasus terburuk di mana semua hash ke satu slot, kunci kedua dibandingkan dengan kunci pertama, kunci ketiga dibandingkan dengan # 1 dan # 2, kunci keempat dibandingkan dengan # 1, # 2, dan # 3 , dll. Pada saat Anda mencapai kunci # 1 juta, Anda telah melakukan lebih dari satu triliun perbandingan.
@ Oscar: Umm, saya tidak melihat bagaimana itu "tidak juga". Ini lebih seperti "biarkan saya menjelaskan". Tapi ya, memang benar bahwa jika Anda membuat entri baru dengan kunci yang sama dengan entri yang sudah ada, ini akan menimpa entri pertama. Itulah yang saya maksud ketika saya berbicara tentang mencari duplikat di paragraf terakhir: Setiap kali kunci hash ke slot yang sama, HashMap harus memeriksa apakah itu duplikat dari kunci yang ada, atau apakah mereka hanya di slot yang sama secara kebetulan dari fungsi hash. Saya tidak tahu bahwa itu adalah "inti" dari HashMap: Saya akan mengatakan bahwa "keseluruhan" adalah bahwa Anda dapat mengambil elemen dengan kunci dengan cepat.
Tapi bagaimanapun, itu tidak mempengaruhi "keseluruhan poin" yang saya coba buat: Ketika Anda memiliki dua kunci - ya, kunci yang berbeda, bukan kunci yang sama yang muncul lagi - itu memetakan ke slot yang sama dalam tabel , HashMap membuat daftar tertaut. Kemudian, karena harus memeriksa setiap kunci baru untuk melihat apakah itu benar-benar duplikat dari kunci yang ada, setiap upaya untuk menambahkan entri baru yang memetakan ke slot yang sama ini harus mengejar daftar tertaut yang memeriksa setiap entri yang ada untuk melihat apakah ini adalah duplikat dari kunci yang terlihat sebelumnya, atau jika itu adalah kunci baru.
Perbarui lama setelah posting asli
Saya baru saja mendapat suara positif untuk jawaban ini 6 tahun setelah posting yang membuat saya membaca ulang pertanyaan itu.
Fungsi hash yang diberikan dalam pertanyaan bukanlah hash yang bagus untuk 26 juta entri.
Ia menambahkan bersama a [0] + a [1] dan b [0] + b [1] + b [2]. Dia mengatakan nilai setiap byte berkisar dari 0 hingga 51, sehingga hanya memberikan (51 * 2 + 1) * (51 * 3 + 1) = 15.862 kemungkinan nilai hash. Dengan 26 juta entri, ini berarti rata-rata sekitar 1639 entri per nilai hash. Itu adalah banyak sekali benturan, membutuhkan banyak sekali pencarian berurutan melalui daftar tertaut.
OP mengatakan bahwa urutan yang berbeda dalam array a dan array b harus dianggap sama, yaitu [[1,2], [3,4,5]]. Sama dengan ([[2,1], [5,3,4] ]), dan untuk memenuhi kontrak mereka harus memiliki kode hash yang sama. Baik. Namun, ada lebih dari 15.000 kemungkinan nilai. Fungsi hash kedua yang diusulkannya jauh lebih baik, memberikan jangkauan yang lebih luas.
Meskipun seperti yang dikomentari orang lain, tampaknya fungsi hash tidak sesuai untuk mengubah data lain. Akan lebih masuk akal untuk "menormalkan" objek saat dibuat, atau meminta fungsi hash bekerja dari salinan array. Selain itu, menggunakan perulangan untuk menghitung konstanta setiap kali melalui fungsi tidak efisien. Karena hanya ada empat nilai di sini, saya akan menuliskannya
yang akan menyebabkan kompilator melakukan kalkulasi sekali pada waktu kompilasi; atau memiliki 4 konstanta statis yang ditentukan di kelas.
Selain itu, draf pertama pada fungsi hash memiliki beberapa kalkulasi yang tidak melakukan apa pun untuk ditambahkan ke rentang keluaran. Perhatikan bahwa ia pertama kali menetapkan hash = 503 daripada mengalikan dengan 5381 bahkan sebelum mempertimbangkan nilai dari kelas. Jadi ... pada dasarnya dia menambahkan 503 * 5381 ke setiap nilai. Apa yang dicapai ini? Menambahkan konstanta ke setiap nilai hash hanya membakar siklus cpu tanpa menyelesaikan sesuatu yang berguna. Pelajaran di sini: Menambahkan kompleksitas ke fungsi hash bukanlah tujuannya. Tujuannya adalah untuk mendapatkan berbagai nilai yang berbeda, bukan hanya untuk menambah kompleksitas demi kompleksitas.
sumber
String.equals( Integer )
isfalse
. Tetapi jika Anda memiliki kelas yang sama (atau setidaknya.equals
mengembalikan nilai true) maka entri yang sama digunakan. Misalnyanew String("one")
dan `string baru (" satu ") digunakan sebagai kunci, akan menggunakan entri yang sama. Sebenarnya ini adalah poin SELURUH HashMap di tempat pertama! Lihat sendiri: pastebin.com/f20af40b9Ide pertama saya adalah memastikan Anda menginisialisasi HashMap dengan tepat. Dari JavaDocs untuk HashMap :
Jadi jika Anda memulai dengan HashMap yang terlalu kecil, maka setiap kali ukurannya perlu diubah, semua hash dihitung ulang ... yang mungkin Anda rasakan saat mencapai 2-3 juta titik penyisipan.
sumber
initialcapactity = maxentries/loadcapacity
(seperti 30M, 0,95 untuk 26M entri) tetapi ini BUKAN kasus Anda, karena Anda mengalami semua tabrakan yang Anda gunakan hanya sekitar 20k atau kurang.Saya menyarankan pendekatan bercabang tiga:
Jalankan Java dengan lebih banyak memori:
java -Xmx256M
misalnya untuk dijalankan dengan 256 Megabyte. Gunakan lebih banyak jika perlu dan Anda memiliki banyak RAM.Cache nilai hash yang dihitung seperti yang disarankan oleh poster lain, jadi setiap objek hanya menghitung nilai hashnya satu kali.
Gunakan algoritme hashing yang lebih baik. Yang Anda posting akan mengembalikan hash yang sama di mana a = {0, 1} seperti di mana a = {1, 0}, semuanya sama.
Manfaatkan apa yang diberikan Java secara gratis.
Saya cukup yakin ini memiliki peluang bentrok yang jauh lebih sedikit daripada metode hashCode Anda yang ada, meskipun itu tergantung pada sifat sebenarnya dari data Anda.
sumber
Masuk ke area abu-abu "on / off topic", tetapi perlu untuk menghilangkan kebingungan terkait saran Oscar Reyes bahwa lebih banyak tabrakan hash adalah hal yang baik karena mengurangi jumlah elemen di HashMap. Saya mungkin salah paham tentang apa yang dikatakan Oscar, tetapi sepertinya saya bukan satu-satunya: kdgregory, delfuego, Nash0, dan saya semua tampaknya memiliki pemahaman (mis) yang sama.
Jika saya mengerti apa yang dikatakan Oscar tentang kelas yang sama dengan kode hash yang sama, dia mengusulkan bahwa hanya satu contoh kelas dengan kode hash yang diberikan akan dimasukkan ke dalam HashMap. Misalnya, jika saya memiliki instance SomeClass dengan hashcode 1 dan instance kedua SomeClass dengan hashcode 1, hanya satu instance SomeClass yang dimasukkan.
Contoh Java pastebin di http://pastebin.com/f20af40b9 tampaknya menunjukkan dengan benar merangkum apa yang Oscar usulkan di atas.
Terlepas dari pemahaman atau kesalahpahaman, apa yang terjadi adalah contoh yang berbeda dari kelas yang sama tidak dimasukkan hanya sekali ke dalam HashMap jika mereka memiliki kode hash yang sama - tidak sampai ditentukan apakah kuncinya sama atau tidak. Kontrak kode hash mengharuskan objek yang sama memiliki kode hash yang sama; Namun, itu tidak mengharuskan objek yang tidak sama memiliki kode hash yang berbeda (meskipun ini mungkin diinginkan karena alasan lain) [1].
Contoh pastebin.com/f20af40b9 (yang dirujuk Oscar setidaknya dua kali) mengikuti, tetapi sedikit dimodifikasi untuk menggunakan pernyataan JUnit daripada printlines. Contoh ini digunakan untuk mendukung proposal bahwa kode hash yang sama menyebabkan benturan dan jika kelasnya sama, hanya satu entri yang dibuat (misalnya, hanya satu String dalam kasus khusus ini):
Namun, kode hash bukanlah cerita lengkapnya. Apa yang pastebin contoh mengabaikan adalah kenyataan bahwa kedua
s
danese
sama: mereka berdua string "ese". Jadi, memasukkan atau mendapatkan konten peta menggunakans
atauese
atau"ese"
sebagai kunci semuanya setara karenas.equals(ese) && s.equals("ese")
.Tes kedua menunjukkan bahwa adalah salah untuk menyimpulkan bahwa kode hash yang identik pada kelas yang sama adalah alasan kunci -> nilai
s -> 1
ditimpaese -> 2
ketikamap.put(ese, 2)
dipanggil dalam tes satu. Dalam pengujian kedua,s
danese
masih memiliki kode hash yang sama (sebagaimana diverifikasi olehassertEquals(s.hashCode(), ese.hashCode());
) DAN mereka adalah kelas yang sama. Namun,s
danese
merupakanMyString
instance dalam pengujian ini, bukanString
instance Java - dengan satu-satunya perbedaan yang relevan untuk pengujian ini adalah sama:String s equals String ese
dalam pengujian satu di atas, sedangkanMyStrings s does not equal MyString ese
dalam pengujian dua:Berdasarkan komentar selanjutnya, Oscar tampaknya membalikkan apa yang dia katakan sebelumnya dan mengakui pentingnya persamaan. Namun, tampaknya gagasan bahwa yang sama adalah yang penting, bukan "kelas yang sama", tidak jelas (penekanan dari saya):
Daftar dibuat hanya jika hashnya sama, tetapi kuncinya berbeda. Misalnya jika String memberikan kode hash 2345 dan dan Integer memberikan kode hash yang sama 2345, maka integer tersebut dimasukkan ke dalam daftar karena String. sama (Integer) salah. Tetapi jika Anda memiliki kelas yang sama (atau setidaknya .equals mengembalikan nilai benar) maka entri yang sama digunakan. Misalnya String baru ("satu") dan `String baru (" satu ") digunakan sebagai kunci, akan menggunakan entri yang sama. Sebenarnya ini adalah poin SELURUH HashMap di tempat pertama! Lihat sendiri: pastebin.com/f20af40b9 - Oscar Reyes "
versus komentar sebelumnya yang secara eksplisit membahas pentingnya kelas identik dan kode hash yang sama, tanpa menyebutkan sama dengan:
"@delfuego: Lihat sendiri: pastebin.com/f20af40b9 Jadi, dalam pertanyaan ini kelas yang sama sedang digunakan (tunggu sebentar, kelas yang sama sedang digunakan kan?) Yang menyiratkan bahwa ketika hash yang sama digunakan, entri yang sama digunakan dan tidak ada "daftar" entri. - Oscar Reyes "
atau
"Sebenarnya ini akan meningkatkan kinerja. Semakin banyak tabrakan eq lebih sedikit entri dalam persamaan hashtable. Lebih sedikit pekerjaan yang harus dilakukan. ciptaan yang kinerjanya merendahkan. - Oscar Reyes "
atau
"@kdgregory: Ya, tetapi hanya jika tabrakan terjadi dengan kelas yang berbeda, untuk kelas yang sama (yang merupakan kasus), entri yang sama digunakan. - Oscar Reyes"
Sekali lagi, saya mungkin salah paham tentang apa yang sebenarnya coba dikatakan Oscar. Namun, komentar aslinya telah menyebabkan kebingungan yang cukup sehingga tampaknya bijaksana untuk menjernihkan semuanya dengan beberapa tes eksplisit sehingga tidak ada keraguan yang tersisa.
[1] - Dari Java yang Efektif, Edisi Kedua oleh Joshua Bloch:
Kapan pun itu dipanggil pada objek yang sama lebih dari sekali selama eksekusi aplikasi, metode hashCode harus secara konsisten mengembalikan bilangan bulat yang sama, asalkan tidak ada informasi yang digunakan dalam perbandingan yang sama pada objek yang dimodifikasi. Integer ini tidak perlu tetap konsisten dari satu eksekusi aplikasi ke eksekusi lain dari aplikasi yang sama.
Jika dua objek sama menurut metode equal s (Obj ect), maka pemanggilan metode hashCode pada masing-masing objek harus menghasilkan hasil integer yang sama.
Tidak diperlukan bahwa jika dua objek tidak sama menurut metode s (Object) yang sama, maka pemanggilan metode hashCode pada masing-masing dari dua objek harus menghasilkan hasil integer yang berbeda. Namun, programmer harus menyadari bahwa menghasilkan hasil integer yang berbeda untuk objek yang tidak sama dapat meningkatkan kinerja tabel hash.
sumber
Jika array dalam kode hash yang Anda posting adalah byte, maka kemungkinan besar Anda akan mendapatkan banyak duplikat.
a [0] + a [1] akan selalu antara 0 dan 512. menambahkan b akan selalu menghasilkan angka antara 0 dan 768. kalikan itu dan Anda mendapatkan batas atas 400.000 kombinasi unik, dengan asumsi data Anda terdistribusi sempurna di antara setiap nilai yang mungkin dari setiap byte. Jika data Anda sama sekali biasa, kemungkinan besar Anda memiliki keluaran yang jauh lebih unik dari metode ini.
sumber
HashMap memiliki kapasitas awal dan performa HashMap sangat bergantung pada hashCode yang menghasilkan objek yang mendasarinya.
Cobalah untuk menyesuaikan keduanya.
sumber
Jika kunci memiliki pola apa pun, Anda dapat membagi peta menjadi peta yang lebih kecil dan memiliki peta indeks.
Contoh: Kunci: 1,2,3, .... n 28 peta yang masing-masing berisi 1 juta. Peta indeks: 1-1.000.000 -> Peta1 1.000.000-2.000.000 -> Peta2
Jadi, Anda akan melakukan dua pencarian tetapi kumpulan kuncinya adalah 1.000.000 vs 28.000.000. Anda juga dapat melakukannya dengan mudah dengan pola sengatan.
Jika kunci benar-benar acak maka ini tidak akan berhasil
sumber
Jika array dua byte yang Anda sebutkan adalah seluruh kunci Anda, nilainya berada dalam kisaran 0-51, unik dan urutan dalam array a dan b tidak signifikan, matematika saya memberi tahu saya bahwa hanya ada sekitar 26 juta kemungkinan permutasi dan bahwa Anda mungkin mencoba mengisi peta dengan nilai untuk semua kemungkinan kunci.
Dalam hal ini, mengisi dan mengambil nilai dari penyimpanan data Anda tentu saja akan jauh lebih cepat jika Anda menggunakan array daripada HashMap dan mengindeksnya dari 0 hingga 25989599.
sumber
Saya terlambat di sini, tetapi beberapa komentar tentang peta besar:
Saya berasumsi bahwa peta ini berumur panjang. yaitu Anda mengisinya dan mereka bertahan selama aplikasi. Saya juga berasumsi bahwa aplikasi itu sendiri berumur panjang - seperti semacam server.
Setiap entri di Java HashMap memerlukan tiga objek: kunci, nilai, dan Entri yang mengikatnya. Jadi 26 juta entri di peta berarti 26 juta * 3 == 78 juta objek. Ini bagus sampai Anda mencapai GC penuh. Maka Anda punya masalah jeda-dunia. GC akan melihat masing-masing 78 juta objek dan menentukan semuanya hidup. 78M + objek hanyalah banyak objek untuk dilihat. Jika aplikasi Anda dapat mentolerir jeda sesekali (mungkin beberapa detik), tidak ada masalah. Jika Anda mencoba mencapai jaminan latensi, Anda dapat mengalami masalah besar (tentu saja jika Anda menginginkan jaminan latensi, Java bukanlah platform yang dapat dipilih :)) Jika nilai di peta Anda berubah dengan cepat, Anda dapat berakhir dengan pengumpulan penuh yang sering yang memperparah masalah.
Saya tidak tahu solusi yang bagus untuk masalah ini. Ide ide:
Sekadar pemikiran dari seseorang yang telah menghabiskan banyak waktu dengan peta raksasa di Jawa.
sumber
Dari percobaan saya (proyek siswa tahun 2009):
Catatan: "Prime Tree" bekerja paling baik pada "kunci kontinu" dari 1 - 10 juta. Untuk bekerja dengan kunci seperti HashMap kita membutuhkan beberapa penyesuaian anak di bawah umur.
Jadi, apa itu #PrimeTree? Singkatnya, ini adalah struktur data pohon seperti Pohon Biner, dengan nomor cabang adalah bilangan prima (bukan biner "2").
sumber
Anda dapat mencoba menggunakan database dalam memori seperti HSQLDB .
sumber
SQLite memungkinkan Anda menggunakannya di memori.
sumber
Pernahkah Anda mempertimbangkan untuk menggunakan database sematan untuk melakukan ini. Lihatlah Berkeley DB . Ini open-source, dimiliki oleh Oracle sekarang.
Ini menyimpan semuanya sebagai Key-> Value pair, BUKAN RDBMS. dan itu bertujuan untuk menjadi cepat.
sumber
Pertama, Anda harus memeriksa apakah Anda menggunakan Map dengan benar, metode hashCode () yang baik untuk kunci, kapasitas awal untuk Map, implementasi Map yang benar, dll. Seperti yang dijelaskan oleh banyak jawaban lain.
Kemudian saya akan menyarankan menggunakan profiler untuk melihat apa yang sebenarnya terjadi dan di mana waktu eksekusi dihabiskan. Apakah, misalnya, metode hashCode () dieksekusi miliaran kali?
Jika itu tidak membantu, bagaimana jika menggunakan sesuatu seperti EHCache atau memcache ? Ya, ini adalah produk untuk penyimpanan cache tetapi Anda dapat mengkonfigurasinya sehingga memiliki kapasitas yang cukup dan tidak akan pernah mengeluarkan nilai apa pun dari penyimpanan cache.
Pilihan lain adalah beberapa mesin database yang bobotnya lebih ringan daripada SQL RDBMS penuh. Sesuatu seperti Berkeley DB , mungkin.
Perhatikan, bahwa saya pribadi tidak memiliki pengalaman dengan kinerja produk ini, tetapi mereka patut untuk dicoba.
sumber
Anda dapat mencoba menyimpan kode hash yang dihitung ke dalam cache ke objek kunci.
Sesuatu seperti ini:
Tentu saja Anda harus berhati-hati agar tidak mengubah konten key setelah hashCode dihitung untuk pertama kali.
Sunting: Tampaknya caching memiliki nilai kode yang tidak berguna ketika Anda menambahkan setiap kunci hanya sekali ke peta. Dalam situasi lain, ini bisa berguna.
sumber
Poster lain sudah menunjukkan bahwa penerapan kode hash Anda akan menghasilkan banyak tabrakan karena cara Anda menambahkan nilai secara bersamaan. Saya bersedia menjadi itu, jika Anda melihat objek HashMap di debugger, Anda akan menemukan bahwa Anda mungkin memiliki 200 nilai hash yang berbeda, dengan rantai keranjang yang sangat panjang.
Jika Anda selalu memiliki nilai dalam rentang 0..51, masing-masing nilai tersebut akan membutuhkan 6 bit untuk diwakili. Jika Anda selalu memiliki 5 nilai, Anda dapat membuat kode hash 30-bit dengan pergeseran kiri dan penambahan:
Pergeseran kiri cepat, tetapi akan meninggalkan Anda dengan kode hash yang tidak terdistribusi secara merata (karena 6 bit menyiratkan kisaran 0..63). Alternatifnya adalah mengalikan hash dengan 51 dan menambahkan setiap nilai. Ini masih tidak akan terdistribusi sempurna (misalnya, {2,0} dan {1,52} akan bertabrakan), dan akan lebih lambat dari shift.
sumber
Seperti yang ditunjukkan, implementasi kode hash Anda memiliki terlalu banyak tabrakan, dan memperbaikinya akan menghasilkan kinerja yang layak. Selain itu, menyimpan kode hash dan menerapkan sama secara efisien akan membantu.
Jika Anda perlu mengoptimalkan lebih jauh:
Berdasarkan uraian Anda, hanya ada (52 * 51/2) * (52 * 51 * 50/6) = 29304600 kunci yang berbeda (26000000, yaitu sekitar 90%, akan hadir). Oleh karena itu, Anda bisa mendesain fungsi hash tanpa benturan, dan menggunakan array sederhana daripada hashmap untuk menyimpan data Anda, mengurangi konsumsi memori dan meningkatkan kecepatan pencarian:
(Secara umum, tidak mungkin untuk merancang fungsi hash yang efisien dan bebas benturan yang terkumpul dengan baik, itulah sebabnya HashMap akan mentolerir tabrakan, yang menimbulkan beberapa overhead)
Dengan asumsi
a
danb
diurutkan, Anda mungkin menggunakan fungsi hash berikut:Saya pikir ini bebas tabrakan. Membuktikan hal ini dibiarkan sebagai latihan bagi pembaca yang cenderung matematis.
sumber
Dalam Java yang Efektif: Panduan Bahasa Pemrograman (Seri Java)
Bab 3 Anda dapat menemukan aturan yang baik untuk diikuti saat menghitung hashCode ().
Khususnya:
Jika bidang adalah larik, perlakukan seolah-olah setiap elemen adalah bidang yang terpisah. Artinya, hitung kode hash untuk setiap elemen penting dengan menerapkan aturan ini secara rekursif, dan gabungkan nilai-nilai ini per langkah 2.b. Jika setiap elemen dalam kolom array signifikan, Anda dapat menggunakan salah satu metode Arrays.hashCode yang ditambahkan dalam rilis 1.5.
sumber
Alokasikan peta besar di awal. Jika Anda tahu itu akan memiliki 26 juta entri dan Anda memiliki memori untuk itu, lakukan a
new HashMap(30000000)
.Yakin, Anda memiliki cukup memori untuk 26 juta entri dengan 26 juta kunci dan nilai? Ini terdengar seperti banyak kenangan bagi saya. Apakah Anda yakin bahwa pengumpulan sampah masih baik-baik saja di angka 2 hingga 3 juta? Saya bisa membayangkan itu sebagai hambatan.
sumber
Anda dapat mencoba dua hal:Buat
hashCode
metode Anda mengembalikan sesuatu yang lebih sederhana dan lebih efektif seperti int berurutanInisialisasi peta Anda sebagai:
Kedua tindakan itu akan sangat mengurangi jumlah pengulangan struktur yang dilakukan, dan menurut saya cukup mudah untuk diuji.
Jika tidak berhasil, pertimbangkan untuk menggunakan penyimpanan yang berbeda seperti RDBMS.
EDIT
Aneh bahwa pengaturan kapasitas awal mengurangi kinerja dalam kasus Anda.
Lihat dari javadocs :
Saya membuat microbeachmark (yang sama sekali tidak pasti tetapi setidaknya membuktikan hal ini)
Jadi, menggunakan kapasitas awal turun dari 21 detik menjadi 16 detik karena perulangan. Itu meninggalkan kami dengan milikmu
hashCode
metode sebagai "area peluang";)EDITBukankah HashMap tersebut
Sesuai edisi terakhir Anda.
Saya pikir Anda harus benar-benar membuat profil aplikasi Anda dan melihat di mana memori / cpu digunakan.
Saya telah membuat kelas yang menerapkan hal yang sama
hashCode
Kode hash tersebut memberikan jutaan tabrakan, kemudian entri di HashMap berkurang secara dramatis.
Saya lulus dari 21s, 16s di tes saya sebelumnya menjadi 10s dan 8s. Alasannya adalah karena kode hash memicu sejumlah besar tabrakan dan Anda tidak menyimpan 26 juta objek yang Anda pikirkan tetapi jumlah yang jauh lebih rendah (sekitar 20k menurut saya) Jadi:
Masalahnya BUKAN HASHMAP ada di tempat lain dalam kode Anda.
Sudah waktunya untuk mendapatkan profiler dan mencari tahu di mana. Saya akan berpikir itu pada pembuatan item atau mungkin Anda menulis ke disk atau menerima data dari jaringan.
Inilah implementasi saya di kelas Anda.
perhatikan saya tidak menggunakan rentang 0-51 seperti yang Anda lakukan tetapi -126 hingga 127 untuk nilai saya dan mengaku berulang, itu karena saya melakukan tes ini sebelum Anda memperbarui pertanyaan Anda
Satu-satunya perbedaan adalah bahwa kelas Anda akan memiliki lebih banyak tabrakan sehingga lebih sedikit item yang disimpan di peta.
Menggunakan kelas ini memiliki kunci untuk program sebelumnya
beri saya:
sumber
Mungkin coba gunakan jika Anda membutuhkannya untuk disinkronkan
http://commons.apache.org/collections/api/org/apache/commons/collections/FastHashMap.html
sumber
Saya melakukan tes kecil beberapa waktu yang lalu dengan daftar vs hashmap, lucunya iterasi melalui daftar dan menemukan objek mengambil jumlah waktu yang sama dalam milidetik seperti menggunakan fungsi get hashmaps ... hanya fyi. Oh ya, memori adalah masalah besar saat bekerja dengan hashmaps sebesar itu.
sumber
Metode hashing populer yang digunakan tidak terlalu bagus untuk set besar dan, seperti yang ditunjukkan di atas, hash yang digunakan sangat buruk. Lebih baik menggunakan algoritma hash dengan pencampuran dan cakupan yang tinggi seperti BuzHash (contoh implementasi di http://www.java2s.com/Code/Java/Development-Class/AveryefficientjavahashalgorithmbasedontheBuzHashalgoritm.htm )
sumber