Zgodnie z moim zrozumieniem myślę:
- Jest całkowicie legalne, aby dwa obiekty miały ten sam kod skrótu.
- Jeśli dwa obiekty są równe (przy użyciu metody equals ()), mają ten sam kod skrótu.
- Jeśli dwa obiekty nie są równe, nie mogą mieć tego samego kodu skrótu
Mam rację?
Teraz, jeśli mam rację, mam następujące pytanie: HashMap
Wewnętrznie używa kodu skrótu obiektu. Więc jeśli dwa obiekty mogą mieć ten sam kod skrótu, to w jaki sposób można HashMap
śledzić, którego klucza używa?
Czy ktoś może wyjaśnić, w jaki sposób HashMap
wewnętrznie wykorzystuje kod skrótu obiektu?
java
hashmap
hashcode
hash-function
akshay
źródło
źródło
Odpowiedzi:
Mapa skrótów działa w ten sposób (jest to nieco uproszczone, ale ilustruje podstawowy mechanizm):
Ma wiele „segmentów”, których używa do przechowywania par klucz-wartość. Każdy segment ma unikalny numer - to właśnie identyfikuje segment. Kiedy umieścisz parę klucz-wartość na mapie, mapa hash spojrzy na kod skrótu klucza i zapisze parę w wiadrze, którego identyfikatorem jest kod skrótu klucza. Na przykład: Kod skrótu klucza to 235 -> para jest przechowywana w segmencie numer 235. (Uwaga: w jednym segmencie można zapisać więcej niż jedną parę klucz-wartość).
Kiedy odszukasz wartość w haszapie, podając jej klucz, najpierw sprawdzi kod hashu podanego klucza. Haszapa następnie zajrzy do odpowiedniego segmentu, a następnie porówna klucz, który podałeś, z kluczami wszystkich par w segmencie, porównując je z
equals()
.Teraz możesz zobaczyć, jak to jest bardzo efektywne w wyszukiwaniu par klucz-wartość na mapie: dzięki kodowi skrótu klucza haszapa natychmiast wie, w którym segmencie ma szukać, więc musi tylko sprawdzić, co jest w tym segmencie.
Patrząc na powyższy mechanizm, możesz również zobaczyć, jakie wymagania są niezbędne w odniesieniu do
hashCode()
iequals()
metod kluczy:Jeśli dwa klucze są takie same (
equals()
zwracatrue
przy porównywaniu), ichhashCode()
metoda musi zwracać ten sam numer. Jeśli klucze naruszą ten warunek, wówczas klucze, które są równe, mogą być przechowywane w różnych segmentach, a mapa hash nie będzie w stanie znaleźć par klucz-wartość (ponieważ będzie wyglądać w tym samym segmencie).Jeśli dwa klucze są różne, nie ma znaczenia, czy ich kody skrótu są takie same, czy nie. Będą one przechowywane w tym samym segmencie, jeśli ich kody skrótu są takie same, aw tym przypadku mapa będzie
equals()
ich rozróżniać.źródło
hashCode()
metoda zwraca różne kody skrótu, wówczas metodyequals()
ihashCode()
klasy klucza naruszają umowę, a przy użyciu tych kluczy w a otrzymasz dziwne wynikiHashMap
.HashMap
, który można znaleźć w plikusrc.zip
w katalogu instalacyjnym JDK.Twoje trzecie twierdzenie jest nieprawidłowe.
Jest całkowicie legalne, aby dwa nierówne obiekty miały ten sam kod skrótu. Jest używany
HashMap
jako „filtr pierwszego przejścia”, dzięki czemu mapa może szybko znaleźć możliwe wpisy za pomocą określonego klucza. Klucze z tym samym kodem skrótu są następnie testowane pod kątem równości z określonym kluczem.Nie chciałbyś wymagania, aby dwa nierówne obiekty nie miały tego samego kodu skrótu, ponieważ w przeciwnym razie ograniczałoby cię to do 2 32 możliwych obiektów. (Oznaczałoby to również, że różne typy nie mogłyby nawet użyć pól obiektu do wygenerowania kodów skrótu, ponieważ inne klasy mogłyby wygenerować ten sam skrót).
źródło
HashMap
to tablicaEntry
obiektów.Traktuj
HashMap
jako tablicę obiektów.Zobacz, co to
Object
jest:Każdy
Entry
obiekt reprezentuje parę klucz-wartość. Polenext
odnosi się do innegoEntry
obiektu, jeśli wiadro ma więcej niż jedenEntry
.Czasami może się zdarzyć, że kody skrótu dla 2 różnych obiektów są takie same. W takim przypadku dwa obiekty zostaną zapisane w jednym segmencie i zostaną przedstawione jako lista połączona. Punktem wejścia jest ostatnio dodany obiekt. Ten obiekt odnosi się do innego obiektu z
next
polem i tak dalej. Ostatni wpis odnosi się donull
.Podczas tworzenia
HashMap
z domyślnym konstruktoremTablica jest tworzona z rozmiarem 16 i domyślnym równoważeniem obciążenia 0,75.
Dodanie nowej pary klucz-wartość
hash % (arrayLength-1)
której element powinien zostać umieszczony (numer łyżki)HashMap
, wartość zostanie zastąpiona.Jeśli wiadro ma już co najmniej jeden element, nowy zostaje dodany i umieszczony w pierwszej pozycji wiadra. Jego
next
pole odnosi się do starego elementu.Usunięcie
hash % (arrayLength-1)
Entry
. Jeśli żądany element nie zostanie znaleziony, zwróćnull
źródło
hash % (arrayLength-1)
byłoby złehash % arrayLength
. Ale tak naprawdę jesthash & (arrayLength-1)
. To znaczy, ponieważ wykorzystuje on potęgę dwóch (2^n
) dla długości tablicy, biorącn
najmniej znaczące bity.int
oczywiście tym, który może być ujemny, wykonanie modulo na liczbie ujemnej da ci liczbę ujemnąDoskonałe informacje można znaleźć na stronie http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html
Podsumowując:
HashMap działa na zasadzie mieszania
put (klucz, wartość): HashMap przechowuje zarówno klucz, jak i wartość obiektu jako Map.Entry. Hashmap stosuje hashcode (klucz), aby uzyskać wiadro. w przypadku kolizji HashMap używa LinkedList do przechowywania obiektu.
get (key): HashMap używa kodu skrótu Key Object, aby znaleźć lokalizację segmentu, a następnie wywołać metodę keys.equals () w celu zidentyfikowania poprawnego węzła na LinkedList i zwrócenia obiektu wartości powiązanej dla tego klucza w Java HashMap.
źródło
Oto ogólny opis
HashMap
mechanizmu dlaJava 8
wersji (może się nieco różnić od Java 6) .Struktury danych
skrótu Wartość skrótu jest obliczana za
hash()
pomocą klawisza i decyduje, którego segmentu tabeli mieszającej użyć dla danego klucza.Gdy liczba elementów w wiadrze jest niewielka, używana jest lista połączona pojedynczo.
Gdy liczba elementów w wiadrze jest duża, stosuje się czerwono-czarne drzewo.
Klasy (wewnętrzne)
Map.Entry
Reprezentuje pojedynczy byt na mapie, klucz / wartość.
HashMap.Node
Wersja połączonej listy węzła.
Może reprezentować:
Ponieważ ma właściwość skrótu.
HashMap.TreeNode
Wersja drzewa węzła.
Pola (wewnętrzne)
Node[] table
Tabela kubełkowa (nagłówek połączonych list).
Jeśli segment nie zawiera elementów, ma wartość null, dlatego zajmuje tylko miejsce odniesienia.
Set<Map.Entry> entrySet
Zestaw bytów.int size
Liczba podmiotów.
float loadFactor
Wskaż, jak pełna jest tabela skrótów, przed zmianą rozmiaru.
int threshold
Następny rozmiar do zmiany rozmiaru.
Formuła:
threshold = capacity * loadFactor
Metody (wewnętrzne)
int hash(key)
Oblicz skrót według klucza.
Jak mapować skrót do wiadra?
Użyj następującej logiki:
O pojemności
W tabeli skrótów pojemność oznacza liczbę łyżek, z której można ją uzyskać
table.length
.Można go również obliczyć za pomocą,
threshold
aloadFactor
zatem nie trzeba definiować go jako pola klasy.Można uzyskać efektywną pojemność poprzez:
capacity()
Operacje
Najpierw znajdź segment według wartości skrótu, a następnie zapętloną listę lub wyszukaj posortowane drzewo.
Najpierw znajdź segment zgodnie z wartością skrótu klucza.
Następnie spróbuj znaleźć wartość:
Po
threshold
osiągnięciu podwoi pojemność hashtable (table.length
), a następnie przeprowadzi ponowne mieszanie wszystkich elementów w celu przebudowania tabeli.To może być kosztowna operacja.
Występ
Złożoność czasu jest
O(1)
, ponieważ:O(1)
.O(1)
.O(1)
nieO(log N)
.źródło
Kod skrótu określa, które wiadro ma sprawdzić skrót. Jeśli w wiadrze znajduje się więcej niż jeden obiekt, przeprowadzane jest wyszukiwanie liniowe w celu znalezienia, który element w wiadrze jest równy pożądanemu przedmiotowi (przy użyciu
equals()
).Innymi słowy, jeśli masz doskonały kod skrótu, to dostęp do planu skrótu jest stały, nigdy nie będziesz musiał iterować przez segment (technicznie musiałbyś również mieć segmenty MAX_INT, implementacja Java może współdzielić kilka kodów skrótu w tym samym segmencie, aby zmniejszone wymagania dotyczące miejsca). Jeśli masz najgorszy kod skrótu (zawsze zwraca ten sam numer), dostęp do mapy skrótów staje się liniowy, ponieważ musisz przeszukać każdy element na mapie (wszystkie są w tym samym segmencie), aby uzyskać to, czego chcesz.
Przez większość czasu dobrze napisany hashcode nie jest idealny, ale jest wystarczająco wyjątkowy, aby dać ci mniej więcej stały dostęp.
źródło
Mylisz się w punkcie trzecim. Dwa wpisy mogą mieć ten sam kod skrótu, ale nie mogą być równe. Spójrz na implementację HashMap.get z OpenJdk . Widać, że sprawdza, czy wartości skrótów są równe, a klucze są równe. Gdyby punkt trzeci był prawdziwy, nie byłoby konieczne sprawdzanie, czy klucze są równe. Kod skrótu jest porównywany przed kluczem, ponieważ ten pierwszy jest bardziej wydajnym porównaniem.
Jeśli chcesz dowiedzieć się więcej na ten temat, zajrzyj do artykułu w Wikipedii na temat rozwiązywania kolizji w Open Addressing , który moim zdaniem jest mechanizmem używanym przez implementację OpenJdk. Mechanizm ten jest nieco inny niż podejście „kubełkowe”, o którym wspomina jedna z pozostałych odpowiedzi.
źródło
Widzimy więc, że jeśli oba obiekty S1 i S2 mają inną zawartość, to jesteśmy prawie pewni, że nasza przesłonięta metoda Hashcode wygeneruje inny Hashcode (116232,11601) dla obu obiektów. TERAZ, ponieważ istnieją różne kody skrótu, więc nawet nie będzie kłopotać się z wywołaniem metody EQUALS. Ponieważ inny kod skrótu GWARANTUJE RÓŻNĄ zawartość w obiekcie.
źródło
Aktualizacja Java 8 w HashMap-
wykonujesz tę operację w swoim kodzie -
Załóżmy więc, że kod skrótu został zwrócony dla obu kluczy
"old"
i"very-old"
jest taki sam. Co się stanie.myHashMap
jest HashMap i załóżmy, że początkowo nie określiłeś jego pojemności. Tak więc domyślna pojemność jak na Javę wynosi 16. Więc jak tylko zainicjowałeś hashap za pomocą nowego słowa kluczowego, stworzyłem 16 segmentów. teraz, kiedy wykonałeś pierwszą instrukcjęnastępnie
"old"
obliczany jest hashcode dla , a ponieważ hashcode może być również bardzo dużą liczbą całkowitą, więc java zrobiła to wewnętrznie - (hash jest hashcode tutaj, a >>> jest prawy shift)tak aby dać jako większy obraz, powróci jakiś indeks, który byłby od 0 do 15. Teraz twój parę klucz wartość
"old"
i"old-value"
będą konwertowane na klucz i wartość zmiennej instancji obiektu wpisu za. a następnie ten obiekt wejściowy zostanie zapisany w segmencie, lub można powiedzieć, że pod określonym indeksem ten obiekt wejściowy zostanie zapisany.FYI- Entry to klasa w interfejsie Map- Map.Entry, z tymi podpisami / definicjami
teraz, gdy wykonasz następną instrukcję -
i
"very-old"
podaje ten sam kod skrótu co"old"
, więc ta nowa para wartości klucza jest ponownie wysyłana do tego samego indeksu lub tego samego segmentu. Ponieważ jednak ten segment nie jest pusty,next
zmienna obiektu Entry służy do przechowywania tej nowej pary wartości klucza.i to będzie przechowywane jako lista połączona dla każdego obiektu, który ma ten sam kod skrótu, ale wartość TRIEFY_THRESHOLD jest określona wartością 6. więc po osiągnięciu tego lista połączona jest przekształcana w drzewo zrównoważone (drzewo czerwono-czarne) z pierwszym elementem jako korzeń.
źródło
Każdy obiekt Entry reprezentuje parę klucz-wartość. Pole następne odnosi się do innego obiektu wejściowego, jeśli wiadro ma więcej niż 1 wpis.
Czasami może się zdarzyć, że hashCodes dla 2 różnych obiektów są takie same. W takim przypadku 2 obiekty zostaną zapisane w jednym segmencie i będą prezentowane jako LinkedList. Punktem wejścia jest ostatnio dodany obiekt. Ten obiekt odnosi się do innego obiektu z następnym polem, a więc jednym. Ostatni wpis odnosi się do null. Podczas tworzenia HashMap z domyślnym konstruktorem
Tablica jest tworzona z rozmiarem 16 i domyślnym równoważeniem obciążenia 0,75.
(Źródło)
źródło
Mapa mieszania działa na zasadzie mieszania
Metoda HashMap get (Key k) wywołuje metodę hashCode na obiekcie klucza i stosuje zwrócony hashValue do własnej statycznej funkcji skrótu w celu znalezienia położenia segmentu (macierzy bazowej), w którym klucze i wartości są przechowywane w formie zagnieżdżonej klasy o nazwie Entry (Map). Wejście). Doszliście więc do wniosku, że z poprzedniego wiersza, że zarówno klucz, jak i wartość są przechowywane w segmencie jako forma obiektu Entry. Myślenie, że w wiadrze przechowywana jest tylko wartość, jest nieprawidłowe i nie wywrze dobrego wrażenia na ankiecie.
Jeśli klucz ma wartość NULL, to klucze Null zawsze są mapowane na wartość skrótu 0, a zatem indeks 0.
Jeśli klucz nie jest pusty, wówczas wywoła funkcję skrótu na obiekcie klucza, patrz wiersz 4 w powyższej metodzie, tj. Key.hashCode (), więc po key.hashCode () zwraca hashValue, wiersz 4 wygląda jak
a teraz stosuje zwrócony hashValue do własnej funkcji skrótu.
Możemy się zastanawiać, dlaczego ponownie obliczamy wartość skrótu za pomocą skrótu (hashValue). Odpowiedź brzmi: chroni przed funkcjami skrótu niskiej jakości.
Teraz końcowa wartość skrótu służy do znalezienia położenia segmentu, w którym przechowywany jest obiekt Entry. Przechowuj obiekty obiektów w wiadrze w ten sposób (skrót, klucz, wartość, bucketindex)
źródło
Nie będę wchodził w szczegóły działania HashMap, ale podam przykład, abyśmy mogli pamiętać, jak działa HashMap, odnosząc go do rzeczywistości.
Mamy Key, Value, HashCode i wiadro.
Przez jakiś czas będziemy odnosić się do każdego z nich:
Za pomocą Map.get (klucz):
Stevie chce dostać się do domu swojego przyjaciela (Josse), który mieszka w willi w społeczności VIP, niech to będzie JavaLovers Society. Adres Jossego to jego SSN (który jest inny dla wszystkich). Prowadzony jest indeks, w którym dowiadujemy się nazwy Towarzystwa na podstawie SSN. Ten indeks można uznać za algorytm służący do wyszukiwania kodu HashCode.
Korzystanie z Map.put (klucz, wartość)
To znajdzie odpowiednie społeczeństwo dla tej Wartości, znajdując HashCode, a następnie wartość jest zapisywana.
Mam nadzieję, że to pomaga i jest otwarte na modyfikacje.
źródło
To będzie długa odpowiedź, weź drinka i czytaj dalej…
Hashowanie polega na przechowywaniu w pamięci pary klucz-wartość, którą można szybciej odczytywać i zapisywać. Przechowuje klucze w tablicy i wartości w LinkedList.
Powiedzmy, że chcę przechowywać 4 pary kluczowych wartości -
Aby przechowywać klucze, potrzebujemy tablicy 4 elementów. Jak teraz odwzorować jeden z tych 4 kluczy na 4 indeksy tablic (0,1,2,3)?
Więc java znajduje hashCode poszczególnych kluczy i mapuje je na określony indeks tablicy. Formuły Hashcode to -
Hash i dziewczyna !! Wiem o czym myślisz. Twoja fascynacja dzikim duetem może sprawić, że przegapisz ważną rzecz.
Dlaczego Java pomnoży to przez 31?
Jak ten kod skrótu jest odwzorowany na indeks tablicy?
Odpowiedź brzmi
Hash Code % (Array length -1)
. Tak“girl”
jest odwzorowane(3173020 % 3) = 1
w naszym przypadku. który jest drugim elementem tablicy.a wartość „ahhan” jest przechowywana w LinkedList powiązanej z indeksem tablicy 1.
HashCollision - Jeśli spróbujesz znaleźć
hasHCode
klucze“misused”
i“horsemints”
użyć opisanych powyżej formuł, zobaczysz, że oba dają nam to samo1069518484
. Whooaa !! wyciągnięta lekcja -Teraz mapa skrótu wygląda następująco -
Teraz, jeśli jakieś ciało spróbuje znaleźć wartość klucza
“horsemints”
, java szybko znajdzie jej hashCode, moduł i zacznie szukać jego wartości w odnośniku LinkedListindex 1
. W ten sposób nie musimy przeszukiwać wszystkich 4 indeksów tablic, co przyspieszy dostęp do danych.Ale poczekaj jedną sekundę. istnieją 3 wartości w tym odnośniku 1 odpowiadającym indeksowi tablicy, w jaki sposób dowiaduje się, która z nich była wartością kluczowych „konników”?
Właściwie skłamałem, kiedy powiedziałem, że HashMap po prostu przechowuje wartości w LinkedList.
Przechowuje obie pary wartości klucza jako wpis mapy. Mapa faktycznie wygląda tak.
Teraz możesz zobaczyć Podczas przechodzenia przez LinkedList odpowiadającą ArrayIndex1 faktycznie porównuje on klucz każdego wpisu do tej LinkedList z „koniami”, a gdy go znajdzie, po prostu zwraca jego wartość.
Mam nadzieję, że dobrze się bawiłeś podczas czytania :)
źródło
Jak powiedziano, obraz jest wart 1000 słów. Mówię: jakiś kod jest lepszy niż 1000 słów. Oto kod źródłowy HashMap. Uzyskaj metodę:
Staje się więc jasne, że skrót znajduje się w celu znalezienia „segmentu”, a pierwszy element jest zawsze sprawdzany w tym segmencie. Jeśli nie, to
equals
klucz służy do znalezienia rzeczywistego elementu na połączonej liście.Zobaczmy
put()
metodę:Jest to nieco bardziej skomplikowane, ale staje się jasne, że nowy element jest umieszczany na karcie w pozycji obliczonej na podstawie skrótu:
i = (n - 1) & hash
otoi
indeks, w którym zostanie umieszczony nowy element (lub jest to „wiadro”).n
to rozmiartab
tablicy (tablica „segmentów”).Najpierw próbuje się go umieścić jako pierwszy element w tym „wiadrze”. Jeśli istnieje już element, dodaj nowy węzeł do listy.
źródło