Java HashMap performans optimizasyonu / alternatifi


102

Büyük bir HashMap oluşturmak istiyorum ancak put()performans yeterince iyi değil. Herhangi bir fikir?

Diğer veri yapısı önerileri memnuniyetle karşılanır, ancak bir Java Haritasının arama özelliğine ihtiyacım var:

map.get(key)

Benim durumumda 26 milyon girişli bir harita oluşturmak istiyorum. Standart Java HashMap'i kullanarak, yerleştirme hızı, 2-3 milyon eklemeden sonra dayanılmaz derecede yavaşlar.

Ayrıca, anahtarlar için farklı karma kod dağıtımları kullanmanın yardımcı olup olmayacağını bilen var mı?

Karma kod yöntemim:

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;
}

Eşit nesnelerin aynı hashcode'a sahip olmasını sağlamak için toplama işleminin ilişkisel özelliğini kullanıyorum. Diziler, 0 - 51 aralığında değerlere sahip baytlardır. Değerler her iki dizide de yalnızca bir kez kullanılır. A dizileri aynı değerleri içeriyorsa (her iki sırada) nesneler eşittir ve aynı şey b dizisi için de geçerlidir. Yani a = {0,1} b = {45,12,33} ve a = {1,0} b = {33,45,12} eşittir.

DÜZENLE, bazı notlar:

  • Birkaç kişi 26 milyon girişi saklamak için bir karma harita veya başka bir veri yapısı kullanmayı eleştirdi. Bunun neden tuhaf göründüğünü anlamıyorum. Bana klasik bir veri yapıları ve algoritmalar problemi gibi görünüyor. 26 milyon öğem var ve bunları hızla ekleyebilmek ve bir veri yapısından arayabilmek istiyorum: bana veri yapısını ve algoritmaları verin.

  • Varsayılan Java HashMap'in başlangıç ​​kapasitesini 26 milyona ayarlamak performansı düşürür .

  • Bazı insanlar, diğer bazı durumlarda kesinlikle akıllı seçenek olan veri tabanlarının kullanılmasını önerdiler. Ama ben gerçekten bir veri yapıları ve algoritmalar sorusu soruyorum, tam bir veritabanı aşırı ve iyi bir veri yapısı çözümünden çok daha yavaş olacaktır (sonuçta veritabanı sadece bir yazılımdır, ancak iletişim ve muhtemelen disk ek yüküne sahip olacaktır).


29
HashMap yavaşlarsa, büyük olasılıkla hash fonksiyonunuz yeterince iyi değildir.
Pascal Cuoq

12
Doktor, acıyor yaptığımda bu
skaffman

12
Bu gerçekten güzel bir soru; hash algoritmalarının neden önemli olduğuna ve performans üzerinde sahip olabileceklerine dair güzel bir gösteri
oxbow_lakes

12
A'ların toplamı 0 ila 102 aralığına sahiptir ve b'lerin toplamı 0 ila 153 aralığına sahiptir, bu nedenle yalnızca 15.606 olası hash değeriniz ve aynı hashCode ile ortalama 1.666 anahtarınız vardır. Karma kodunuzu, olası karma kodların sayısı anahtar sayısından çok daha fazla olacak şekilde değiştirmelisiniz.
Peter Lawrey

6
Texas Hold 'Em Poker'i modellediğinize psişik olarak karar verdim ;-)
bacar

Yanıtlar:


56

Birçok kişinin işaret ettiği gibi, hashCode()yöntem suçluydu. Yalnızca 26 milyon farklı nesne için yaklaşık 20.000 kod üretiyordu. Bu, hash kova başına ortalama 1.300 nesnedir = çok çok kötü. Bununla birlikte, iki diziyi 52 tabanındaki bir sayıya çevirirsem, her nesne için benzersiz bir hash kodu elde edeceğim:

public int hashCode() {       
    // assume that both a and b are sorted       
    return a[0] + powerOf52(a[1], 1) + powerOf52(b[0], 2) + powerOf52(b[1], 3) + powerOf52(b[2], 4);
}

public static int powerOf52(byte b, int power) {
    int result = b;
    for (int i = 0; i < power; i++) {
        result *= 52;
    }
    return result;
}

Diziler, bu yöntemlerin hashCode()eşit nesnelerin aynı hash koduna sahip olduğu sözleşmesini yerine getirmesini sağlamak için sıralanır . Eski yöntemi kullanarak, 100.000 koyma blokları üzerinden saniyedeki ortalama koyma sayısı, 100.000 ile 2.000.000 arasındaydı:

168350.17
109409.195
81344.91
64319.023
53780.79
45931.258
39680.29
34972.676
31354.514
28343.062
25562.371
23850.695
22299.22
20998.006
19797.799
18702.951
17702.434
16832.182
16084.52
15353.083

Yeni yöntemi kullanmak şunları verir:

337837.84
337268.12
337078.66
336983.97
313873.2
317460.3
317748.5
320000.0
309704.06
310752.03
312944.5
265780.75
275540.5
264350.44
273522.97
270910.94
279008.7
276285.5
283455.16
289603.25

Çok daha iyi. Yeni yöntem iyi bir iş hacmini korurken eski yöntem çok hızlı bir şekilde sona erdi.


17
hashCodeYöntemdeki dizileri değiştirmemenizi öneririm . Geleneksel olarak, hashCodenesnenin durumunu değiştirmez. Belki de kurucu bunları sınıflandırmak için daha iyi bir yer olabilir.
Michael Myers

Dizilerin sıralanmasının yapıcıda olması gerektiğine katılıyorum. Gösterilen kod hiçbir zaman hashCode'u ayarlamaz. Kod hesaplanması aşağıdaki gibi basit yapılabilir: int result = a[0]; result = result * 52 + a[1]; //etc.
rsp

Yapıcıda sıralamanın ve ardından karma kodunu mmyers ve rsp'nin önerdiği şekilde hesaplamanın daha iyi olduğunu kabul ediyorum. Benim durumumda çözümüm kabul edilebilir ve dizilerin çalışması için sıralanması gerektiği gerçeğini vurgulamak istedim hashCode().
nash

3
Ayrıca, karma kodu önbelleğe alabileceğinizi (ve nesneniz değiştirilebilirse uygun şekilde geçersiz kılabileceğinizi) unutmayın.
NateS

1
Sadece java.util.Arrays.hashCode () kullanın . Daha basittir (kendi başınıza yazıp bakımını yapacak kod yoktur), hesaplaması muhtemelen daha hızlıdır (daha az çarpma) ve hash kodlarının dağıtımı muhtemelen daha eşit olacaktır.
jcsahnwaldt Reinstate Monica

18

Ben senin fark bir şey hashCode()yöntemiyle dizi elemanları sırası olduğunu a[]ve b[]mesele yok. Böylece (a[]={1,2,3}, b[]={99,100}), ile aynı değere hash olacaktır (a[]={3,1,2}, b[]={100,99}). Aslında tüm anahtarlar k1ve k2nerede sum(k1.a)==sum(k2.a)ve sum(k1.b)=sum(k2.b)çarpışmalara neden olacak. Dizinin her bir konumuna bir ağırlık atamanızı öneririm:

hash = hash * 5381 + (c0*a[0] + c1*a[1]);
hash = hash * 5381 + (c0*b[0] + c1*b[1] + c3*b[2]);

nerede c0, c1ve c3olan ayrı sabitleri (eğer farklı sabitleri kullanabilirsiniz bgerekirse). Bu işleri biraz daha düzene sokmalı.


Bununla birlikte, benim için işe yaramayacağını da eklemeliyim, çünkü farklı sıralarda aynı elemanlara sahip dizilerin aynı hashcode vermesini istiyorum.
nash

5
Bu durumda, 52C2 + 52C3 hashcode'larınız var (hesap makineme göre 23426) ve bir hashmap, iş için oldukça yanlış bir araçtır.
kdgregory

Aslında bu, performansı artıracaktır. Daha fazla çarpışma, hashtable eq'de daha az giriş demektir. yapacak daha az iş. Karma (iyi görünen) ya da hashtable (harika çalışan) değil mi, bahse girerim performansın düştüğü nesne oluşturma üzerinedir.
OscarRyz

7
@Oscar - daha fazla çarpışma, yapılacak daha çok iş demektir, çünkü artık hash zincirinde doğrusal bir arama yapmanız gerekiyor. Eşittir () başına 26.000.000 farklı değeriniz ve hashCode () başına 26.000 farklı değeriniz varsa, kova zincirlerinin her birinde 1.000 nesne olur.
kdgregory

@ Nash0: Bunların aynı hashCode'a sahip olmasını, ancak aynı zamanda eşit olmamasını (equals () yöntemiyle tanımlandığı gibi) istediğinizi söylüyorsunuz. Neden bunu istiyorsun?
MAK

17

Pascal'ı detaylandırmak için: Bir HashMap'in nasıl çalıştığını anlıyor musunuz? Karma tablonuzda birkaç yuva var. Her anahtarın karma değeri bulunur ve ardından tablodaki bir girişe eşlenir. İki hash değeri aynı girişle eşleşiyorsa - bir "hash collision" - HashMap bağlantılı bir liste oluşturur.

Karma çarpışmalar, bir karma haritanın performansını öldürebilir. Aşırı durumda, tüm anahtarlarınız aynı hash koduna sahipse veya farklı hash kodlarına sahipse ancak hepsi aynı yuvaya eşleniyorsa, hash haritanız bağlantılı bir listeye dönüşür.

Dolayısıyla, performans sorunları görüyorsanız, kontrol edeceğim ilk şey şudur: Karma kodların rastgele görünen bir dağılımını alıyor muyum? Değilse, daha iyi bir hash fonksiyonuna ihtiyacınız vardır. Bu durumda "daha iyi", "belirli veri kümem için daha iyi" anlamına gelebilir. Örneğin, dizelerle çalıştığınızı ve karma değeri için dizenin uzunluğunu aldığınızı varsayalım. (Java'nın String.hashCode çalıştığı gibi değil, ama ben sadece basit bir örnek oluşturuyorum.) Dizeleriniz 1'den 10.000'e kadar değişen uzunluklara sahipse ve bu aralıkta oldukça eşit bir şekilde dağıtılmışsa, bu çok iyi olabilir Özet fonksiyonu. Ancak dizelerinizin tümü 1 veya 2 karakter ise, bu çok kötü bir hash işlevi olacaktır.

Düzenleme: Eklemeliyim: Her yeni giriş eklediğinizde, HashMap bunun bir kopya olup olmadığını kontrol eder. Bir hash çarpışması olduğunda, gelen anahtarı o yuvaya eşlenen her anahtarla karşılaştırması gerekir. Dolayısıyla, her şeyin tek bir yuvaya hash olduğu en kötü durumda, ikinci anahtar ilk anahtarla, üçüncü anahtar # 1 ve # 2 ile, dördüncü anahtar # 1, # 2 ve # 3 ile karşılaştırılır , vb. 1 milyon anahtarına ulaştığınızda, bir trilyondan fazla karşılaştırma yapmış olursunuz.

@Oscar: Umm, bunun nasıl "gerçekten değil" olduğunu anlamıyorum. Daha çok bir "açıklamama izin ver" gibi. Ancak evet, mevcut bir girişle aynı anahtarla yeni bir giriş yaparsanız, bu ilk girişin üzerine yazılır. Son paragrafta kopyaları aramaktan bahsettiğimde bunu kastetmiştim: Aynı yuvaya bir anahtar karması olduğunda, HashMap mevcut bir anahtarın kopyası mı yoksa sadece aynı yuvada mı olduğunu kontrol etmelidir. Özet fonksiyonu. Bunun bir HashMap'in "bütün noktası" olduğunu bilmiyorum: "Bütün mesele", öğeleri anahtarla hızlı bir şekilde geri getirebilmenizdir diyebilirim.

Ama yine de, bu, yapmaya çalıştığım "bütün noktayı" etkilemiyor: İki anahtarınız olduğunda - evet, farklı anahtarlar, aynı anahtar tekrar görünmüyor - bu, tablodaki aynı yuvaya HashMap bağlantılı bir liste oluşturur. Daha sonra, her yeni anahtarı kontrol etmesi gerektiğinden, aslında mevcut bir anahtarın bir kopyası olup olmadığını görmek için, bu aynı yuvaya eşlenen yeni bir giriş ekleme girişimi, bunun olup olmadığını görmek için mevcut her girişi inceleyen bağlantılı listeyi takip etmelidir. önceden görülen bir anahtarın kopyasıdır veya yeni bir anahtar ise.

Orijinal gönderiden çok sonra güncelleyin

Gönderdikten 6 yıl sonra bu cevap için bir oylama aldım ve bu da soruyu yeniden okumama neden oldu.

Soruda verilen karma işlevi 26 milyon giriş için iyi bir karma değildir.

A [0] + a [1] ve b [0] + b [1] + b [2] 'yi toplar. Her baytın değerinin 0 ile 51 arasında olduğunu söylüyor, bu nedenle yalnızca (51 * 2 + 1) * (51 * 3 + 1) = 15.862 olası hash değerleri veriyor. 26 milyon girişle bu, karma değer başına ortalama yaklaşık 1639 giriş anlamına gelir. Bu, bağlantılı listelerde çok sayıda ve çok sayıda ardışık arama gerektiren çok sayıda çarpışmadır.

OP, a dizisi ve b dizisi içindeki farklı sıraların eşit kabul edilmesi gerektiğini söyler, yani [[1,2], [3,4,5]]. Equals ([[2,1], [5,3,4] ]) ve dolayısıyla sözleşmeyi yerine getirmek için eşit karma kodlara sahip olmaları gerekir. Tamam. Yine de 15.000'den fazla olası değer var. Önerdiği ikinci hash işlevi çok daha iyi ve daha geniş bir aralık sağlıyor.

Bir başkasının yorumladığı gibi, bir hash fonksiyonunun diğer verileri değiştirmesi uygunsuz görünüyor. Nesneyi yaratılırken "normalleştirmek" veya hash fonksiyonunun dizilerin kopyalarından çalışması daha mantıklı olacaktır. Ayrıca, işlev aracılığıyla her seferinde sabitleri hesaplamak için bir döngü kullanmak verimsizdir. Burada sadece dört değer olduğu için, ya yazardım

return a[0]+a[1]*52+b[0]*52*52+b[1]*52*52*52+b[2]*52*52*52*52;

bu, derleyicinin hesaplamayı derleme zamanında bir kez gerçekleştirmesine neden olur; veya sınıfta tanımlanmış 4 statik sabiti vardır.

Ayrıca, bir hash fonksiyonundaki ilk taslak, çıktı aralığına eklemek için hiçbir şey yapmayan birkaç hesaplamaya sahiptir. Sınıftaki değerleri dikkate almadan önce hash = 503'ü 5381 ile çarptığına dikkat edin. Yani ... aslında her değere 503 * 5381 ekler. Bu neyi başarır? Her hash değerine bir sabit eklemek, yararlı hiçbir şey başarmadan sadece cpu döngülerini yakar. Buradan çıkarılacak ders: Bir hash fonksiyonuna karmaşıklık eklemek hedef değildir. Amaç, yalnızca karmaşıklık uğruna karmaşıklık eklemek değil, geniş bir yelpazede farklı değerler elde etmektir.


3
Evet, kötü bir hash işlevi bu tür davranışlara neden olur. +1
Henning

Pek sayılmaz. Liste yalnızca hash aynıysa ancak anahtar farklıysa oluşturulur . Örneğin, bir String 2345 karma kodunu verirse ve Tamsayı aynı karma kodu 2345 verirse, tamsayı listeye eklenir çünkü String.equals( Integer )is false. Ancak aynı sınıfa sahipseniz (veya en azından .equalstrue döndürürseniz), o zaman aynı girdi kullanılır. Örneğin new String("one")ve anahtar olarak kullanılan "yeni Dize (" bir "), aynı girişi kullanacaktır. Aslında bu ilk etapta HashMap'in BÜTÜN noktası! Kendiniz görün: pastebin.com/f20af40b9
OscarRyz

3
@Oscar: Orijinal gönderime eklenmiş cevabıma bakın.
Jay

Bunun çok eski bir konu olduğunu biliyorum, ancak hash kodlarıyla ilgili olduğu için burada "çarpışma" terimi için bir referans var: link . Eğer aynı anahtarla başka bir değer koyarak HashMap bir değer yerine, bu edilir değil çarpışma denir
Tahir Akhtar

@Tahir Kesinlikle. Belki de yazım kötü yazılmıştı. Açıklama için teşekkürler.
Jay

7

İlk fikrim, HashMap'inizi uygun şekilde başlattığınızdan emin olmaktır. HashMap için JavaDocs'tan :

Bir HashMap örneğinin performansını etkileyen iki parametresi vardır: başlangıç ​​kapasitesi ve yük faktörü. Kapasite, karma tablosundaki bölümlerin sayısıdır ve başlangıç ​​kapasitesi, basitçe karma tablo oluşturulduğu andaki kapasitedir. Yük faktörü, kapasitesi otomatik olarak artırılmadan önce hash tablosunun ne kadar dolu olmasına izin verildiğinin bir ölçüsüdür. Karma tablosundaki girişlerin sayısı, yük faktörünün ve mevcut kapasitenin çarpımını aştığında, karma tablo yeniden görevlendirilir (yani, dahili veri yapıları yeniden oluşturulur), böylece karma tablo, yaklaşık iki kat daha fazla kepçe içerir.

Dolayısıyla, çok küçük bir HashMap ile başlıyorsanız, yeniden boyutlandırması gerektiğinde, tüm hash'ler yeniden hesaplanır ... 2-3 milyon ekleme noktasına geldiğinizde hissettiğiniz şey bu olabilir.


Yeniden hesaplandıklarını sanmıyorum, asla. Tablo boyutu artırılır, karmalar tutulur.
Henning

Hashmap, her girdi için bit bilge yapar: newIndex = storedHash & newLength;
Henning

4
Hanning: Belki delfügo açısından zayıf bir ifade, ama konu geçerli. Evet, hashCode () çıktısının yeniden hesaplanmaması anlamında karma değerleri yeniden hesaplanmaz. Ancak tablo boyutu artırıldığında, tüm anahtarların tabloya yeniden yerleştirilmesi gerekir, yani tablodan yeni bir yuva numarası almak için hash değerinin yeniden hashing edilmesi gerekir.
Jay

Jay, evet - gerçekten kötü sözler ve söylediklerin. :)
delfuego

1
@delfuego ve @ nash0: Yeap, başlangıç ​​kapasitesini eleman sayısına eşit ayarlamak performansı düşürür çünkü tonlarca çarpışma yaşıyorsunuz ve bu nedenle bu kapasitenin sadece küçük bir kısmını kullanıyorsunuz. Mevcut tüm girişleri kullansanız bile , aynı kapasitenin ayarlanması durumu daha da kötüleştirecektir! Çünkü yük faktörü nedeniyle daha fazla alan talep edilecektir. Kullanmanız gerekecek initialcapactity = maxentries/loadcapacity(30M, 26M girişleri için 0.95 gibi), ancak bu sizin durumunuz DEĞİL , çünkü yalnızca 20k veya daha azını kullandığınız tüm çarpışmaları yaşıyorsunuz.
OscarRyz

7

Üç yönlü bir yaklaşım öneririm:

  1. Java'yı daha fazla bellekle java -Xmx256Mçalıştırın : örneğin 256 Megabayt ile çalıştırmak için. Gerekirse daha fazlasını kullanın ve çok fazla RAM'iniz var.

  2. Hesaplanan karma değerlerinizi başka bir posterin önerdiği şekilde önbelleğe alın, böylece her nesne kendi karma değerini yalnızca bir kez hesaplar.

  3. Daha iyi bir hash algoritması kullanın. Gönderdiğiniz, a = {0, 1} olduğunda, a = {1, 0} durumunda olduğu gibi aynı hash'i döndürecektir, diğer her şey eşittir.

Java'nın size sunduklarından ücretsiz yararlanın.

public int hashCode() {
    return 31 * Arrays.hashCode(a) + Arrays.hashCode(b);
}

Verilerinizin tam olarak doğasına bağlı olmasına rağmen, bunun mevcut hashCode yönteminizden çok daha az çakışma şansı olduğundan eminim.


RAM, bu tür haritalar ve diziler için çok küçük olabilir, bu yüzden zaten bir bellek sınırlaması sorunundan şüphelendim.
ReneS

7

"Açık / kapalı konu" gri alanına girmek, ancak Oscar Reyes ile ilgili kafa karışıklığını gidermek için gerekli, HashMap'teki öğelerin sayısını azalttığı için daha fazla hash çarpışmasının iyi bir şey olduğunu öne sürüyor. Oscar'ın söylediklerini yanlış anlayabilirim, ancak tek kişi gibi görünmüyorum: kdgregory, delfuego, Nash0 ve sanırım hepimiz aynı (yanlış) anlayışı paylaşıyorum.

Oscar'ın aynı hashcode ile aynı sınıf hakkında söylediklerini anlarsam, HashMap'e belirli bir hashcode ile bir sınıfın yalnızca bir örneğinin eklenmesini öneriyor. Örneğin, karma kodu 1 olan bir SomeClass örneğine ve karma kodu 1 olan ikinci bir SomeClass örneğine sahipsem, yalnızca bir SomeClass örneği eklenir.

Http://pastebin.com/f20af40b9 adresindeki Java pastebin örneği , yukarıdakinin Oscar'ın önerdiği şeyi doğru bir şekilde özetlediğini gösteriyor gibi görünüyor.

Ne olursa olsun herhangi bir anlayış ya da yanlış anlama, ne olur aynı sınıfın farklı örnekleri do not o anahtarları eşit olup olmadığı belirlenmektedir olmayacağım kadar - aynı karma kodunun varsa HashMap içine sadece bir kez takılı olsun. Karma kod sözleşmesi, eşit nesnelerin aynı karma koduna sahip olmasını gerektirir; ancak, eşit olmayan nesnelerin farklı karma kodlara sahip olmasını gerektirmez (ancak bu başka nedenlerle istenebilir) [1].

Pastebin.com/f20af40b9 örneği (Oscar'ın en az iki kez ifade ettiği) aşağıdaki gibidir, ancak baskı satırları yerine JUnit iddialarını kullanmak için biraz değiştirilmiştir. Bu örnek, aynı karma kodların çakışmalara neden olduğu ve sınıflar aynı olduğunda yalnızca bir giriş oluşturulduğu önerisini desteklemek için kullanılır (örneğin, bu özel durumda yalnızca bir String):

@Test
public void shouldOverwriteWhenEqualAndHashcodeSame() {
    String s = new String("ese");
    String ese = new String("ese");
    // same hash right?
    assertEquals(s.hashCode(), ese.hashCode());
    // same class
    assertEquals(s.getClass(), ese.getClass());
    // AND equal
    assertTrue(s.equals(ese));

    Map map = new HashMap();
    map.put(s, 1);
    map.put(ese, 2);
    SomeClass some = new SomeClass();
    // still  same hash right?
    assertEquals(s.hashCode(), ese.hashCode());
    assertEquals(s.hashCode(), some.hashCode());

    map.put(some, 3);
    // what would we get?
    assertEquals(2, map.size());

    assertEquals(2, map.get("ese"));
    assertEquals(3, map.get(some));

    assertTrue(s.equals(ese) && s.equals("ese"));
}

class SomeClass {
    public int hashCode() {
        return 100727;
    }
}

Ancak, hashcode hikayenin tamamı değildir. Ne pastebin örneği ihmaller de o gerçektir sve eseonlar dize hem "ese" şunlardır: eşittir. Bu nedenle, haritanın içeriğini anahtar olarak sveya eseveya kullanarak eklemek veya almak "ese"eşdeğerdir çünkü s.equals(ese) && s.equals("ese").

> Değeri - İkinci test anahtarı nedenidir aynı sınıfta o özdeş hashcodes sonuçlandırmak hatalı olduğunu gösteriyor s -> 1tarafından üzerine yazılır ese -> 2zaman map.put(ese, 2)deney birinde denir. İkinci testte sve esehala aynı hashcode'a sahip (tarafından doğrulandığı gibi assertEquals(s.hashCode(), ese.hashCode());) VE bunlar aynı sınıf. Ancak, sve eseolan MyStringörnekler değil, Java, bu testte Stringörnekleri - tek farkla eşittir olmak bu test için ilgili: String s equals String eseyukarıdaki test birinde, oysa MyStrings s does not equal MyString esetestinde iki:

@Test
public void shouldInsertWhenNotEqualAndHashcodeSame() {
    MyString s = new MyString("ese");
    MyString ese = new MyString("ese");
    // same hash right?
    assertEquals(s.hashCode(), ese.hashCode());
    // same class
    assertEquals(s.getClass(), ese.getClass());
    // BUT not equal
    assertFalse(s.equals(ese));

    Map map = new HashMap();
    map.put(s, 1);
    map.put(ese, 2);
    SomeClass some = new SomeClass();
    // still  same hash right?
    assertEquals(s.hashCode(), ese.hashCode());
    assertEquals(s.hashCode(), some.hashCode());

    map.put(some, 3);
    // what would we get?
    assertEquals(3, map.size());

    assertEquals(1, map.get(s));
    assertEquals(2, map.get(ese));
    assertEquals(3, map.get(some));
}

/**
 * NOTE: equals is not overridden so the default implementation is used
 * which means objects are only equal if they're the same instance, whereas
 * the actual Java String class compares the value of its contents.
 */
class MyString {
    String i;

    MyString(String i) {
        this.i = i;
    }

    @Override
    public int hashCode() {
        return 100727;
    }
}

Daha sonraki bir yoruma dayanarak, Oscar daha önce söylediklerini tersine çeviriyor ve eşitlerin önemini kabul ediyor. Ancak yine de önemli olan "aynı sınıf" değil, eşittir kavramı belirsiz görünüyor (vurgu benim):

"Gerçekte değil. Liste yalnızca hash aynıysa, ancak anahtar farklıysa oluşturulur. Örneğin, bir Dize 2345 karma kodunu verirse ve Tamsayı aynı karma kodu 2345 verirse, tamsayı listeye eklenir çünkü String. eşittir (Tamsayı) yanlıştır. Ancak aynı sınıfa sahipseniz (veya en azından .equals, doğru döndürürse) , aynı giriş kullanılır. Örneğin, yeni Dize ("bir") ve "yeni Dize (" bir ") olarak kullanılır tuşları, aynı girişi kullanacaktır. Aslında bu, HashMap'in BÜTÜN noktasıdır! Kendiniz görün: pastebin.com/f20af40b9 - Oscar Reyes "

eşit sınıfın ve aynı karma kodun önemine açıkça değinen önceki yorumlara kıyasla, eşitlerden söz edilmeden:

"@delfuego: Kendiniz görün: pastebin.com/f20af40b9 Yani, bu soruda aynı sınıf kullanılıyor (bir dakika bekleyin, aynı sınıf doğru kullanılıyor mu?) Bu, aynı hash kullanıldığında aynı girişin kullanıldığı anlamına gelir kullanılmış ve girişlerin "listesi" yok. - Oscar Reyes "

veya

"Aslında bu, performansı artıracaktır. Çarpışmalar ne kadar fazlaysa, hashtable denkleminde daha az giriş yapılması gereken daha az iş var. Hash (iyi görünen) ya da hashtable (harika çalışıyor) değil, bahse girerim nesnenin üzerindedir performansın aşağılayıcı olduğu tasarım. - Oscar Reyes "

veya

"@kdgregory: Evet, ancak yalnızca çarpışma farklı sınıflarda meydana gelirse, aynı sınıf için (durum budur) aynı giriş kullanılır. - Oscar Reyes"

Yine, Oscar'ın gerçekte ne söylemeye çalıştığını yanlış anlayabilirim. Bununla birlikte, orijinal yorumları yeterince kafa karışıklığına neden oldu ve her şeyi bazı açık testlerle temizlemenin akıllıca göründüğü için hiçbir şüphe kalmadı.


[1] - Effective Java, Second Edition , Joshua Bloch'dan:

  • Bir uygulamanın yürütülmesi sırasında aynı nesne üzerinde birden çok kez çağrıldığında, nesnede eşit s karşılaştırmalarında kullanılan hiçbir bilgi değiştirilmediği sürece, hashCode yöntemi tutarlı bir şekilde aynı tamsayıyı döndürmelidir. Bu tam sayının, bir uygulamanın bir yürütmesinden aynı uygulamanın başka bir yürütmesine kadar tutarlı kalması gerekmez.

  • İki nesne eşit s (Nesne) yöntemine göre eşitse, iki nesnenin her birinde hashCode yönteminin çağrılması aynı tam sayı sonucunu vermelidir.

  • İki nesne eşittir s (Nesne) yöntemine göre eşit değilse, bu durumda iki nesnenin her birinde hashCode yönteminin çağrılmasının farklı tam sayı sonuçları üretmesi gerekli değildir. Bununla birlikte, programcı, eşit olmayan nesneler için farklı tam sayı sonuçları üretmenin, karma tabloların performansını artırabileceğinin farkında olmalıdır.


5

Gönderilen hashCode'unuzdaki diziler bayt ise, büyük olasılıkla birçok kopya ile sonuçlanacaksınız.

a [0] + a [1] her zaman 0 ile 512 arasında olacaktır. b'lerin eklenmesi her zaman 0 ile 768 arasında bir sayı ile sonuçlanacaktır. Bunları çarpın ve verilerinizin mükemmel bir şekilde dağıtıldığını varsayarak 400.000 benzersiz kombinasyon üst sınırı elde edin her baytın olası her değeri arasında. Verileriniz tamamen düzenliyse, bu yöntemin çok daha az benzersiz çıktılarına sahip olursunuz.


4

HashMap başlangıç ​​kapasitesine sahiptir ve HashMap'in performansı, temeldeki nesneleri üreten hashCode'a çok bağlıdır.

İkisini de değiştirmeyi deneyin.


4

Tuşların herhangi bir düzeni varsa, haritayı daha küçük haritalara bölebilir ve bir dizin haritasına sahip olabilirsiniz.

Örnek: Anahtarlar: 1,2,3, .... n Her biri 1 milyon olan 28 harita. Dizin haritası: 1-1.000.000 -> Harita1 1.000.000-2.000.000 -> Harita2

Yani iki arama yapacaksınız, ancak anahtar seti 1.000.000 ve 28.000.000 olacaktır. Bunu iğne desenleri ile de kolaylıkla yapabilirsiniz.

Anahtarlar tamamen rastgele ise bu işe yaramaz


1
Anahtarlar rastgele olsa bile, bu anahtar / değer çiftinin saklanacağı bir harita seçmek için (key.hashCode ()% 28) kullanabilirsiniz.
Juha Syrjälä

4

Bahsettiğiniz iki bayt dizisi tüm anahtarınızsa, değerler 0-51 aralığındaysa, benzersizse ve a ve b dizilerindeki sıra önemsizse, matematiğim bana yalnızca yaklaşık 26 milyon olası permütasyon olduğunu ve haritayı olası tüm anahtarlar için değerlerle doldurmaya çalışıyorsunuzdur.

Bu durumda, bir HashMap yerine bir dizi kullanırsanız ve 0'dan 25989599'a indekslerseniz, veri deponuzdaki değerleri doldurmak ve almak elbette çok daha hızlı olacaktır.


Bu çok iyi bir fikir ve aslında bunu 1.2 milyar öğeli başka bir veri depolama sorunu için yapıyorum. Bu durumda, kolay yoldan çıkmak ve önceden hazırlanmış bir veri yapısı kullanmak istedim :)
nash

4

Burada geç kaldım, ancak büyük haritalar hakkında birkaç yorum:

  1. Diğer gönderilerde ayrıntılı olarak tartışıldığı gibi, iyi bir hashCode () ile, bir Haritadaki 26 milyon giriş önemli değildir.
  2. Ancak, burada potansiyel olarak gizli bir sorun, dev haritaların GC etkisidir.

Bu haritaların uzun ömürlü olduğunu varsayıyorum. yani onları doldurursunuz ve uygulama süresince takılırlar. Ayrıca uygulamanın kendisinin uzun ömürlü olduğunu varsayıyorum - bir tür sunucu gibi.

Java HashMap'teki her giriş üç nesne gerektirir: bunları birbirine bağlayan anahtar, değer ve Giriş. Yani haritadaki 26 milyon giriş 26 milyon * 3 == 78 milyon nesne anlamına gelir. Tam bir GC'ye ulaşana kadar bu iyidir. O halde dünyayı duraklatma problemin var. GC, 78M nesnelerinin her birine bakacak ve hepsinin canlı olduğunu belirleyecektir. 78M + nesneler, bakılması gereken çok sayıda nesnedir. Uygulamanız ara sıra uzun (belki birkaç saniye) duraklamalara tahammül edebiliyorsa, sorun yoktur. Herhangi bir gecikme garantisine ulaşmaya çalışıyorsanız, büyük bir sorununuz olabilir (tabii ki gecikme garantileri istiyorsanız, Java seçilecek bir platform değildir :)) Haritalarınızdaki değerler hızla değişirse, sık sık tam koleksiyonlarla sonuçlanabilirsiniz. problemi büyük ölçüde karmaşıklaştırır.

Bu sorun için harika bir çözüm bilmiyorum. Fikirler:

  • Bazen tam GC'leri "çoğunlukla" önlemek için GC ve yığın boyutlarını ayarlamak mümkündür.
  • Harita içeriğiniz çok fazla karışırsa, Javolution'ın Hızlı Haritasını deneyebilirsiniz - Giriş nesnelerini bir araya getirebilir, bu da tam toplama sıklığını düşürebilir
  • Kendi harita uygulamanızı oluşturabilir ve bayt [] üzerinde açık bellek yönetimi yapabilirsiniz (yani, milyonlarca nesneyi tek bir bayta [] - ugh!) Serileştirerek daha öngörülebilir gecikme için işlemci ticareti yapabilirsiniz.)
  • Bu kısım için Java kullanmayın - bir soket üzerinden bir tür öngörülebilir bellek içi DB ile konuşun
  • Yeni G1 toplayıcısının yardımcı olacağını umuyoruz (esas olarak yüksek kayıp durumu için geçerlidir)

Java'daki dev haritalarla çok zaman geçirmiş birinden bazı düşünceler.



3

Benim durumumda 26 milyon girişli bir harita oluşturmak istiyorum. Standart Java HashMap'i kullanarak, yerleştirme hızı, 2-3 milyon eklemeden sonra dayanılmaz derecede yavaşlar.

Deneyimden (2009'daki öğrenci projesi):

  • 1'den 100.000'e kadar 100.000 düğüm için bir Kırmızı Siyah Ağaç inşa ettim. 785.68 saniye (13 dakika) sürdü. Ve 1 milyon düğüm için RBTree oluşturmayı başaramadım (HashMap ile elde ettiğiniz sonuçlar gibi).
  • Algoritma veri yapım "Prime Tree" yi kullanarak. 21.29 saniye içinde 10 milyon düğüm için bir ağaç / harita oluşturabilirim (RAM: 1.97Gb). Arama anahtar / değer çifti maliyeti O (1).

Not: "Prime Tree", 1 - 10 milyon "sürekli tuşlarda" en iyi şekilde çalışır. HashMap gibi tuşlarla çalışmak için bazı küçük ayarlamalara ihtiyacımız var.


Peki, #PrimeTree nedir? Kısacası, İkili Ağaç gibi bir ağaç veri yapısıdır, dal numaraları asal sayılardır ("2" ikili yerine).


Lütfen biraz bağlantı veya uygulama paylaşır mısınız?
Benj



1

Bunu yapmak için bir gömülü veritabanı kullanmayı düşündünüz mü? Bak Berkeley DB . Açık kaynak kodlu, şu anda Oracle'a ait.

Her şeyi Anahtar-> Değer çifti olarak depolar, bu bir RDBMS DEĞİLDİR. ve hızlı olmayı hedefliyor.


2
Berkeley DB, serileştirme / GÇ ek yükü nedeniyle bu sayıdaki girişler için yeterince hızlı değildir; asla bir hashmap'ten daha hızlı olamaz ve OP ısrarla ilgilenmez. Öneriniz iyi değil.
oxbow_lakes

1

Öncelikle, Map'i doğru kullandığınızı, anahtarlar için iyi hashCode () yöntemini, Harita için başlangıç ​​kapasitesini, doğru Harita uygulamasını vb. Diğer birçok yanıtın açıkladığı gibi, kontrol etmelisiniz.

O zaman gerçekte ne olduğunu ve yürütme süresinin nerede harcandığını görmek için bir profil oluşturucu kullanmayı öneririm. Örneğin, hashCode () yöntemi milyarlarca kez çalıştırılıyor mu?

Bu yardımcı olmazsa, EHCache veya memcached gibi bir şey kullanmaya ne dersiniz ? Evet, bunlar önbelleğe almaya yönelik ürünlerdir ancak bunları, yeterli kapasiteye sahip olacakları ve önbellekten hiçbir değeri çıkarmayacakları şekilde yapılandırabilirsiniz.

Diğer bir seçenek, tam SQL RDBMS'den daha hafif olan bazı veritabanı motorları olabilir. Berkeley DB gibi bir şey , belki.

Bu ürünlerin performansıyla ilgili kişisel olarak hiç deneyimim olmadığını, ancak denemeye değer olabileceğini unutmayın.


1

Hesaplanmış hash kodunu anahtar nesneye önbelleğe almayı deneyebilirsiniz.

Bunun gibi bir şey:

public int hashCode() {
  if(this.hashCode == null) {
     this.hashCode = computeHashCode();
  }
  return this.hashCode;
}

private int computeHashCode() {
   int hash = 503;
   hash = hash * 5381 + (a[0] + a[1]);
   hash = hash * 5381 + (b[0] + b[1] + b[2]);
   return hash;
}

Elbette hashCode ilk defa hesaplandıktan sonra anahtarın içeriğini değiştirmemeye dikkat etmelisiniz.

Düzenleme: Her anahtarı bir haritaya yalnızca bir kez eklediğinizde önbelleğe almanın kod değerlerine sahip olduğu görülüyor. Başka bir durumda bu yararlı olabilir.


Aşağıda belirtildiği gibi, yeniden boyutlandırıldığında bir HashMap'teki nesnelerin karma kodlarının yeniden hesaplanması yoktur, bu nedenle bu size hiçbir şey kazandırmaz.
delfuego

1

Başka bir poster, hashcode uygulamanızın, birlikte değerler eklemeniz nedeniyle birçok çarpışmaya neden olacağını belirtti. Bir hata ayıklayıcıda HashMap nesnesine bakarsanız, son derece uzun kova zincirleri ile belki de 200 farklı hash değerine sahip olduğunuzu göreceksiniz.

Her zaman 0..51 aralığında değerlere sahipseniz, bu değerlerin her birinin temsil edilmesi 6 bit alacaktır. Her zaman 5 değeriniz varsa, sola kaydırma ve eklemelerle 30 bitlik bir karma kod oluşturabilirsiniz:

    int code = a[0];
    code = (code << 6) + a[1];
    code = (code << 6) + b[0];
    code = (code << 6) + b[1];
    code = (code << 6) + b[2];
    return code;

Sola kaydırma hızlıdır, ancak sizi eşit olarak dağıtılmayan karma kodlarla bırakacaktır (çünkü 6 bit, 0..63 aralığını ifade eder). Bir alternatif, karmayı 51 ile çarpıp her bir değeri eklemektir. Bu yine de mükemmel bir şekilde dağıtılmayacak (örneğin, {2,0} ve {1,52} çakışacak) ve geçişten daha yavaş olacaktır.

    int code = a[0];
    code *= 51 + a[1];
    code *= 51 + b[0];
    code *= 51 + b[1];
    code *= 51 + b[2];
    return code;

@kdgregory: Başka bir yerde "daha fazla çarpışma daha fazla iş anlamına gelir" hakkında yanıt verdim :)
OscarRyz

1

Belirtildiği gibi, karma kod uygulamanızda çok fazla çarpışma var ve bunu düzeltmek iyi performansla sonuçlanmalıdır. Dahası, hashCodes'u önbelleğe almak ve eşit değerleri verimli bir şekilde uygulamak yardımcı olacaktır.

Daha da optimize etmeniz gerekiyorsa:

Açıklamanıza göre, sadece (52 * 51/2) * (52 * 51 * 50/6) = 29304600 farklı anahtar (26000000, yani yaklaşık% 90'ı olacak) var. Bu nedenle, herhangi bir çarpışma olmadan bir karma işlevi tasarlayabilir ve verilerinizi tutmak için bir karma harita yerine basit bir dizi kullanarak bellek tüketimini azaltabilir ve arama hızını artırabilirsiniz:

T[] array = new T[Key.maxHashCode];

void put(Key k, T value) {
    array[k.hashCode()] = value;

T get(Key k) {
    return array[k.hashCode()];
}

(Genel olarak, iyi bir şekilde kümelenen verimli, çarpışmasız bir hash işlevi tasarlamak imkansızdır, bu nedenle bir HashMap, bazı ek yüklere neden olan çarpışmaları tolere edecektir)

Varsayarsak ave bsıralanır, aşağıdaki karma işlevi kullanabilirsiniz:

public int hashCode() {
    assert a[0] < a[1]; 
    int ahash = a[1] * a[1] / 2 
              + a[0];

    assert b[0] < b[1] && b[1] < b[2];

    int bhash = b[2] * b[2] * b[2] / 6
              + b[1] * b[1] / 2
              + b[0];
    return bhash * 52 * 52 / 2 + ahash;
}

static final int maxHashCode = 52 * 52 / 2 * 52 * 52 * 52 / 6;  

Bunun çarpışmasız olduğunu düşünüyorum. Bunu kanıtlamak matematiksel eğilimli okuyucu için bir alıştırma olarak bırakılmıştır.


1

In Etkili Java: Programlama Dili Kılavuzu (Java Serisi)

Bölüm 3 hashCode () hesaplanırken izlenecek iyi kurallar bulabilirsiniz.

Özellikle:

Alan bir diziyse, her öğe ayrı bir alanmış gibi davranın. Yani, bu kuralları yinelemeli olarak uygulayarak her bir önemli öğe için bir karma kodu hesaplayın ve bu değerleri 2.b adımına göre birleştirin. Bir dizi alanındaki her öğe önemliyse, sürüm 1.5'te eklenen Arrays.hashCode yöntemlerinden birini kullanabilirsiniz.


0

Başlangıçta büyük bir harita tahsis edin. 26 milyon girişe sahip olacağını biliyorsanız ve bunun için hafızanız varsa, a new HashMap(30000000).

26 milyon anahtar ve değer içeren 26 milyon giriş için yeterli belleğiniz olduğundan emin misiniz? Bu bana çok fazla hafıza gibi geliyor. Çöp toplama işleminin hala 2 ila 3 milyon işaretinizde iyi durumda olduğundan emin misiniz? Bunu bir darboğaz olarak hayal edebiliyorum.


2
Oh, başka bir şey. Haritadaki tek konumlarda büyük bağlantılı listelerden kaçınmak için karma kodlarınızın eşit olarak dağıtılması gerekir.
ReneS

0

İki şeyi deneyebilirsiniz:

  • hashCodeYönteminizin ardışık int gibi daha basit ve daha etkili bir şey döndürmesini sağlayın

  • Haritanızı şu şekilde başlatın:

    Map map = new HashMap( 30000000, .95f );

Bu iki eylem, yapının yaptığı yeniden düzenleme miktarını büyük ölçüde azaltacak ve bence test edilmesi oldukça kolay.

Bu işe yaramazsa, RDBMS gibi farklı bir depolama alanı kullanmayı düşünün.

DÜZENLE

Başlangıç ​​kapasitesini ayarlamanın sizin durumunuzdaki performansı düşürmesi gariptir.

Javadocs'tan bakın :

Başlangıç ​​kapasitesi, yük faktörüne bölünen maksimum giriş sayısından daha büyükse, yeniden doldurma işlemi asla gerçekleşmez.

Bir mikro plaj işareti yaptım (hiçbir şekilde kesin değildir, ancak en azından bu noktayı kanıtlar)

$cat Huge*java
import java.util.*;
public class Huge {
    public static void main( String [] args ) {
        Map map = new HashMap( 30000000 , 0.95f );
        for( int i = 0 ; i < 26000000 ; i ++ ) { 
            map.put( i, i );
        }
    }
}
import java.util.*;
public class Huge2 {
    public static void main( String [] args ) {
        Map map = new HashMap();
        for( int i = 0 ; i < 26000000 ; i ++ ) { 
            map.put( i, i );
        }
    }
}
$time java -Xms2g -Xmx2g Huge

real    0m16.207s
user    0m14.761s
sys 0m1.377s
$time java -Xms2g -Xmx2g Huge2

real    0m21.781s
user    0m20.045s
sys 0m1.656s
$

Bu nedenle, başlangıç ​​kapasitesini kullanmak, yeniden düzenleme nedeniyle 21 saniyeden 16 saniyeye düşer. Bu bizi hashCodebir "fırsat alanı" olarak yönteminizle bırakır ;)

DÜZENLE

HashMap değil

Son baskınıza göre.

Bence uygulamanızın profilini gerçekten çıkarmalısınız ve belleğin / cpu'nun nerede tüketildiğini görmelisiniz.

Ben de aynısını uygulayan bir sınıf oluşturdum hashCode

Bu karma kod milyonlarca çarpışma verir, ardından HashMap'teki girişler önemli ölçüde azalır.

Önceki testimde 21'lerden, 16'lardan 10'lara ve 8'lere geçtim. Bunun nedeni, hashCode'un çok sayıda çarpışmaya neden olması ve düşündüğünüz 26M nesneyi değil, çok daha düşük bir sayıyı (yaklaşık 20k söyleyebilirim) depolamanızdır.

Sorunlar HASHMAP DEĞİL , kodunuzun başka bir yerindedir .

Profilci edinmenin ve nerede olduğunu bulmanın zamanı geldi. Bence öğenin oluşturulmasında ya da muhtemelen diske yazıyorsunuz veya ağdan veri alıyorsunuz.

İşte sınıfınıza uyguladığım uygulama.

Not Sizin yaptığınız gibi 0-51 aralığı kullanmadım, ancak değerlerim için -126 ila 127 arasında bir aralık kullanmadım ve tekrarlandığını kabul ediyorum, çünkü bu testi sorunuzu güncellemeden önce yaptım

Tek fark, sınıfınızın daha fazla çarpışmaya sahip olması, dolayısıyla haritada daha az eşya depolanmasıdır.

import java.util.*;
public class Item {

    private static byte w = Byte.MIN_VALUE;
    private static byte x = Byte.MIN_VALUE;
    private static byte y = Byte.MIN_VALUE;
    private static byte z = Byte.MIN_VALUE;

    // Just to avoid typing :) 
    private static final byte M = Byte.MAX_VALUE;
    private static final byte m = Byte.MIN_VALUE;


    private byte [] a = new byte[2];
    private byte [] b = new byte[3];

    public Item () {
        // make a different value for the bytes
        increment();
        a[0] = z;        a[1] = y;    
        b[0] = x;        b[1] = w;   b[2] = z;
    }

    private static void increment() {
        z++;
        if( z == M ) {
            z = m;
            y++;
        }
        if( y == M ) {
            y = m;
            x++;
        }
        if( x == M ) {
            x = m;
            w++;
        }
    }
    public String toString() {
        return "" + this.hashCode();
    }



    public int hashCode() {
        int hash = 503;
        hash = hash * 5381 + (a[0] + a[1]);
        hash = hash * 5381 + (b[0] + b[1] + b[2]);
        return hash;
    }
    // I don't realy care about this right now. 
    public boolean equals( Object other ) {
        return this.hashCode() == other.hashCode();
    }

    // print how many collisions do we have in 26M items.
    public static void main( String [] args ) {
        Set set = new HashSet();
        int collisions = 0;
        for ( int i = 0 ; i < 26000000 ; i++ ) {
            if( ! set.add( new Item() ) ) {
                collisions++;
            }
        }
        System.out.println( collisions );
    }
}

Bu sınıfın kullanılması önceki program için Anahtar'a sahiptir

 map.put( new Item() , i );

bana verir:

real     0m11.188s
user     0m10.784s
sys 0m0.261s


real     0m9.348s
user     0m9.071s
sys  0m0.161s

3
Oscar, yukarıda başka yerlerde de belirtildiği gibi (yorumlarınıza yanıt olarak), daha fazla çarpışmanın İYİ olduğunu varsayıyor gibisiniz; bu çok iyi DEĞİL. Bir çarpışma, belirli bir karmadaki yuvanın tek bir giriş içermekten girişlerin bir listesini içermesine kadar gittiği anlamına gelir ve bu listenin, yuvaya her erişildiğinde aranması / geçilmesi gerekir.
delfuego

@delfuego: Gerçekten değil, bu yalnızca farklı sınıfları kullanarak bir çarpışma olduğunda, ancak aynı sınıf için aynı giriş kullanıldığında olur;)
OscarRyz

2
@Oscar - MAK'ın cevabıyla size cevabımı görün. HashMap, her karma grupta bağlantılı bir giriş listesi tutar ve her öğede equals () öğesini çağıran bu listeyi yürütür. Nesnenin sınıfının onunla hiçbir ilgisi yoktur (equals () üzerindeki bir kısa devre dışında).
kdgregory

1
@Oscar - Cevabınızı okurken, hashcode'lar aynıysa equals () 'ın true döndüreceğini varsayıyorsunuz. Bu, eşittir / karma kod sözleşmesinin bir parçası değildir. Yanlış anladıysam, bu yorumu dikkate almayın.
kdgregory

1
Oscar çabanız için çok teşekkür ederim ama bence anahtar nesnelerin eşit olmasıyla aynı hash koduna sahip olması arasında kafa karıştırıyorsunuz. Ayrıca kod bağlantılarınızdan birinde anahtar olarak eşit dizeleri kullanıyorsunuz, Java'daki dizelerin değişmez olduğunu unutmayın. Sanırım ikimiz de bugün hash hakkında çok şey öğrendik :)
nash


0

Bir süre önce bir listeye karşı bir hashmap ile küçük bir test yaptım, komik bir şey listeyi yineliyordu ve nesneyi bulmak, hashmaps get işlevini kullanmakla aynı süreyi milisaniye cinsinden alıyordu ... sadece bir fyi. Oh evet hafıza, bu boyuttaki karma haritalarla çalışırken büyük bir sorundur.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.