Zły pomysł, aby użyć klucza String w HashMap?
Rozumiem, że metoda String' hashCode() jest a nie gwarantowana do generowania unikalnych kodów hash dla różnych string-s. Wiele z tego użycia może powodować poważne problemy z aplikacją, jeśli Mapa put
wyparła wpis Hashmapy, który został wcześniej umieszczony na mapie za pomocą naprawdę odrębnego klucza Łańcuchowego.
Jakie są szanse, że trafisz na scenariusz, w którym ciąg znaków.hashCode () zwraca tę samą wartość dla distinct String-s? Jak programiści obejdą ten problem, gdy kluczem jest ciąg znaków?
5 answers
Programiści nie muszą zajmować się problemem kolizji hash w Hashmapie, aby uzyskać poprawność programu.
Jest tu kilka kluczowych rzeczy do zrozumienia:
-
Kolizje są nieodłączną cechą haszowania i muszą być. Liczba możliwych wartości (łańcuchów w Twoim przypadku, ale dotyczy również innych typów) jest znacznie większa niż zakres liczb całkowitych.
- każde użycie hashingu ma sposób na kolizji, a Kolekcje Javy (w tym HashMap) nie są wyjątkiem.
- hashowanie nie bierze udziału w testowaniu równości. Prawdą jest, że równe obiekty muszą mieć równe hashcody, ale odwrotność nie jest prawdziwa: wiele wartości będzie miało ten sam hashcode. Więc nie próbuj używać porównania hashcode jako substytutu równości. Kolekcje nie. używają hashowania, aby wybrać podzbiory (zwane bucketem w świecie kolekcji Java), ale używają .equals () to actually check dla równości.
- nie tylko nie musisz się martwić o kolizje powodujące błędne wyniki w kolekcji, ale w większości aplikacji również * zwykle * nie musisz się martwić o wydajność-zbiory zaszyfrowane w Javie świetnie radzą sobie z zarządzaniem hashcodami.
- Jeszcze lepiej, w przypadku, o którym pytałeś (ciągi jako klucze), nie musisz nawet martwić się o same hashcody, ponieważ Klasa String Javy generuje całkiem dobry hashcode. Podobnie jak większość dostarczonych klas Javy.
Trochę więcej szczegółów, jeśli chcesz:
Sposób, w jaki działa haszowanie (w szczególności w przypadku haszowanych kolekcji, takich jak HashMap Java, o co pytałeś) jest następujący:
HashMap przechowuje podane wartości w kolekcji podzbiorów, zwanych buckets. Są one faktycznie realizowane jako listy połączone. Jest ich ograniczona liczba: iirc, domyślnie 16, A Liczba zwiększa się w miarę umieszczania kolejnych przedmiotów na mapie. Zawsze powinno być więcej wiader niż wartości. Aby podać jeden przykład, używając domyślnych wartości, jeśli dodasz 100 wpisów do Hashmapy, będzie 256 wiadrów.
Każda wartość, która może być użyta jako klucz na mapie, musi być w stanie wygenerować wartość całkowitą, zwaną hashcode.
HashMap używa tego hashcode, aby wybrać wiadro. Ostatecznie oznacza to przyjęcie wartości całkowitej
modulo
Liczby kubełków, ale wcześniej HashMap w Javie ma wewnętrzną metodę (o nazwiehash()
), która poprawia hashcode, aby zmniejszyć niektóre znane źródła zlepiania.Podczas wyszukiwania wartości, HashMap wybiera wiadro, a następnie wyszukuje pojedynczy element za pomocą wyszukiwania liniowego połączonej listy, używając
.equals()
.
Więc: nie musisz obejść kolizji dla poprawności i zwykle nie musisz się o nie martwić dla wydajności, a jeśli używasz natywnej Javy klasy (jak String), nie musisz się też martwić o generowanie wartości hashcode.
W przypadku, gdy musisz napisać własną metodę hashcode (co oznacza, że napisałeś klasę z wartością złożoną, jak para imię/nazwisko), sprawy stają się nieco bardziej skomplikowane. Jest całkiem możliwe, aby się pomylić tutaj, ale to nie jest rocket science. Po pierwsze, wiedz jedno: jedyną rzeczą, którą musisz zrobić, aby zapewnić poprawność, jest zapewnienie, że równe obiekty dają równe hashcodes. Więc jeśli piszesz metodę hashcode () dla swojej klasy, musisz również napisać metodę equals() i musisz sprawdzić te same wartości w każdej z nich.
Możliwe jest napisanie metody hashcode (), która jest zła, ale poprawna, przez co mam na myśli, że spełniałaby ograniczenie "równe obiekty muszą dawać równe hashcody" , ale nadal działa bardzo słabo, przez wiele kolizji.
Kanonicznym zwyrodnieniem byłoby napisanie metody, która po prostu zwraca wartość stała (np. 3) dla wszystkich przypadków. Oznaczałoby to, że każda wartość byłaby zahaszowana w tym samym koszyku.
To nadal działa , ale wydajność pogorszyłaby się do tej z połączonej listy.
Oczywiście nie napiszesz tak strasznej metody hashcode (). Jeśli używasz przyzwoitego IDE, jest w stanie wygenerować go dla Ciebie. Ponieważ StackOverflow kocha kod, oto kod dla klasy firstname/lastname powyżej.
public class SimpleName {
private String firstName;
private String lastName;
public SimpleName(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result
+ ((lastName == null) ? 0 : lastName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SimpleName other = (SimpleName) obj;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
return true;
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-07 15:17:58
Mocno podejrzewam, że HashMap.put
metoda nie określa czy klucz jest taki sam Patrząc naString.hashCode
.
Na pewno będzie szansa na kolizję hashową, więc można by się spodziewać, że String.equals
metoda będzie również wywoływana, aby upewnić się, że String
s są rzeczywiście równe, jeśli rzeczywiście istnieje przypadek, w którym dwa String
s mają tę samą wartość zwróconą z hashCode
.
Dlatego nowy klucz String
będzie być oceniane tylko jako ten sam klucz String
Jak ten, który jest już w HashMap
wtedy i tylko wtedy, gdy wartość zwracana przez hashCode
jest równa, a equals
metoda zwraca true
.
Dodam również, że ta myśl byłaby również prawdziwa dla klas innych niż String
, ponieważ Object
sama klasa ma już hashCode
oraz equals
metody.
Edit
Więc, aby odpowiedzieć na pytanie, nie, nie byłoby złym pomysłem, aby użyćString
dla klucza do HashMap
.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-04 14:56:21
To nie jest problem, chodzi tylko o to, jak działają Hashtable. Niemożliwe jest posiadanie odrębnych hashcodów dla wszystkich odrębnych ciągów, ponieważ istnieje znacznie więcej odrębnych ciągów niż liczby całkowite.
Jak pisali inni, kolizje hash są rozwiązywane za pomocą metody equals (). Jedynym problemem, który może to spowodować, jest degeneracja hashtable, co prowadzi do złej wydajności. Dlatego HashMap w Javie ma współczynnik obciążenia , stosunek między kubełkami a wstawionymi elementami, który gdy przekroczony, spowoduje ponowne rozmycie stołu z dwukrotną liczbą wiadrów.
Ogólnie działa to bardzo dobrze, ale tylko wtedy, gdy funkcja hash jest dobra, tzn. nie powoduje więcej niż statystycznie oczekiwana liczba kolizji dla danego zestawu danych wejściowych. String.hashCode()
jest dobry pod tym względem, ale nie zawsze tak było. rzekomo , przed Javą 1.2 to tylko inlcuded każdy n ' - ty znak. Było to szybsze, ale powodowało przewidywalne kolizje dla wszystkich ciągów dzielących każde n ' th charakter - bardzo źle, jeśli jesteś na tyle pecha, aby mieć takie regularne wejście, lub jeśli ktoś chce zrobić atak DOS na Twojej aplikacji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-04 15:20:43
Kieruję Cię do odpowiedzi tutaj . Chociaż nie jest to zły pomysł , aby używać łańcuchów( @CPerkins wyjaśnił dlaczego, doskonale), przechowywanie wartości w hashmapie z integer keys jest lepsze, ponieważ generalnie jest szybsze (chociaż niezauważalnie) i ma mniejsze szanse (właściwie nie ma szans) na kolizje.
Zobacz ten wykres kolizji za pomocą 216553 kluczy w każdym przypadku, (skradziony z tego post , sformatowany dla naszego dyskusja)
Hash Lowercase Random UUID Numbers ============= ============= =========== ============== Murmur 145 ns 259 ns 92 ns 6 collis 5 collis 0 collis FNV-1a 152 ns 504 ns 86 ns 4 collis 4 collis 0 collis FNV-1 184 ns 730 ns 92 ns 1 collis 5 collis 0 collis* DBJ2a 158 ns 443 ns 91 ns 5 collis 6 collis 0 collis*** DJB2 156 ns 437 ns 93 ns 7 collis 6 collis 0 collis*** SDBM 148 ns 484 ns 90 ns 4 collis 6 collis 0 collis** CRC32 250 ns 946 ns 130 ns 2 collis 0 collis 0 collis Avg Time per key 0.8ps 2.5ps 0.44ps Collisions (%) 0.002% 0.002% 0%
Oczywiście, liczba liczb całkowitych jest ograniczona do 2^32, gdzie nie ma limitu liczby łańcuchów (i nie ma teoretycznego limitu liczby kluczy, które mogą być przechowywane w HashMap
). Jeśli użyjesz long
(lub nawet float
), kolizje będą nieuniknione, a zatem nie "lepsze"niż ciąg znaków. Jednak nawet pomimo kolizji skrótu put()
i get()
zawsze będą umieszczać / uzyskiwać poprawną parę klucz-wartość(patrz edycja poniżej).
In the end, to naprawdę nie ma znaczenia, więc używaj tego, co jest wygodniejsze. Ale jeśli wygoda nie robi różnicy i nie zamierzasz mieć więcej niż 2^32 wpisów, proponuję użyć ints
jako kluczy.
EDIT
Chociaż powyższe jest zdecydowanie prawdziwe, nigdy nie używaj "StringKey".hashCode() generuje klucz zamiast oryginalnego klucza String
ze względu na wydajność - 2 różne łańcuchy mogą mieć ten sam hashCode, powodując nadpisanie Twojej metody put()
. Implementacja Javy of HashMap
jest wystarczająco inteligentny, aby obsługiwać ciągi znaków (właściwie każdy rodzaj klucza) z tym samym hashcode automatycznie, więc dobrze jest pozwolić Javie obsługiwać te rzeczy za Ciebie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-12 07:31:17
Mówisz o kolizjach haszyszu. Kolizje Hash są problemem bez względu na typ, który jest hashCode ' D. wszystkie klasy, które używają hashCode (np. HashMap) obsługują kolizje hash dobrze. Na przykład HashMap może przechowywać wiele obiektów w jednym zasobniku.
Nie przejmuj się tym, chyba że sam dzwonisz do hashCode. Kolizje haszyszu, choć rzadkie, niczego nie psują.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-04 14:50:46