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?

Author: Marcus Leon, 2009-10-04

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.

  1. każde użycie hashingu ma sposób na kolizji, a Kolekcje Javy (w tym HashMap) nie są wyjątkiem.

  2. 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.

  3. 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.

  4. 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 nazwie hash()), 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;
    }
}

 109
Author: CPerkins,
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 Strings 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.
 4
Author: coobird,
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.

 4
Author: Michael Borgwardt,
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.

 4
Author: dberm22,
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ą.

 2
Author: Keith Randall,
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