Większość z Was zgodzi się
Więcej na ten temat możesz przeczytać tutaj: Praca z metodą hashCode i równa się w Javie
HashMap
, że jest to obecnie najpopularniejszy temat rozmów podczas rozmów kwalifikacyjnych. Czasami prowadziłem podobne dyskusje z kolegami i naprawdę mi to pomogło. Teraz przeprowadzę z tobą taką dyskusję. Zakładam, że jeśli interesują Cię elementy wewnętrzne i działanie HashMap, to znasz już podstawy HashMap , więc pominę tę część. Jeśli jednak jesteś w tym nowy, sugeruję przejście do witryny Java Docs . Zanim przejdziemy dalej, gorąco polecam zapoznanie się z moim poprzednim artykułem: Praca z hashCode i metodą równości w Javie. Treść tego artykułu:
- Jedyna możliwa odpowiedź.
- Co to jest haszowanie.
- Trochę o klasie
Entry
. - Co robi
put()
. - Jak działa metoda
get()
. - Notatki
Jedyna możliwa odpowiedź
Jeśli ktoś poprosi mnie o wyjaśnienie „ Jak działa HashMap?” „, odpowiem po prostu: „ Zgodnie z zasadami Hashowania ”. To nie mogłoby być prostsze. Aby to zrozumieć i uzyskać rozszerzoną odpowiedź, musisz upewnić się, że znasz podstawy haszowania. Prawidłowy?Co to jest haszowanie
Haszowanie w najprostszej formie to sposób na konwersję dowolnej zmiennej/obiektu na unikalny kod po zastosowaniu dowolnej formuły/algorytmu do ich właściwości. Prawdziwa funkcja skrótu musi spełniać następującą zasadę: Funkcja skrótu musi zwracać ten sam kod skrótu, gdy zostanie zastosowana do tych samych lub równych obiektów. Innymi słowy, dwa identyczne obiekty muszą po kolei zwracać te same kody skrótu.Uwaga: Wszystkie obiekty w Javie dziedziczą standardową implementację hashCode() funkcji opisanej w klasie Object . Funkcja ta zwraca kod mieszający uzyskany poprzez konwersję wewnętrznego adresu obiektu na liczbę, co prowadzi do utworzenia unikalnego kodu dla każdego pojedynczego obiektu. |
Trochę o klasie Entry
Z definicji mapa to „obiekt przechowujący wartości i klucze w parach”. Całkiem proste, prawda? Zatem musi istnieć jakiś mechanizm w HashMap, który przechowuje pary wartości i kluczy? Odpowiedź - tak.HashMap
ma klasę wewnętrzną Entry
, która wygląda następująco:
static class Entry implements Map.Entry
{
final K key;
V value;
Entry next;
final int hash;
...//остальной kod тут…
}
Oczywiście klasa Entry
ma klucz i wartość przechowywane jako atrybuty. Klucz jest oznaczony jako final
i widzimy też dwa dodatkowe pola: next
i hash
. Postaramy się zrozumieć cel tych pól w miarę postępu artykułu.
Do czego służy metoda put() w języku Java?
Zanim zagłębimy się w implementację metodyput()
, bardzo ważne jest, aby zrozumieć, że instancje klasy Entry
są przechowywane w tablicy. Klasa HashMap definiuje tę zmienną jako:
/**
* Размер таблицы, изменяется при необходимости. Длина всегда должна быть
* кратна двум!
*/
transient Entry[] table;
Teraz spójrz na kod implementacji metody put()
:
/**
* Связывает определенное oznaczający с определенным ключом в этой карте(map).
* Если карта перед этим содержала oznaczający для данного ключа, это oznaczający
* заменится на новое.
*
* @param key
* ключ с которым указанное oznaczający должно быть связано.
* @param value
* oznaczający которое должно быть связано с ключом.
* @return вернет предыдущее oznaczający связанное с key, Lub null
* если не было значений связанных с key. (Вернет null
* так же, если перед этим key был связан со oznaczającyм null)
*/
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<k , V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
Rozwiążmy to krok po kroku:
- Przede wszystkim sprawdzamy, czy klucz istnieje. Jeśli klucz nie istnieje (
null
), wartość jest umieszczana w tabeli na pozycji zero, ponieważ kod skrótu wartości tonull
,это – всегда 0
. - W kolejnym kroku wartość skrótu wyliczana jest na podstawie kodu skrótu klucza uzyskanego w wyniku wywołania metody
hashCode()
. Ta wartość skrótu służy do obliczenia pozycji w tablicy, w której obiekt zostanie umieszczonyEntry
. Projektanci JDK założyli, że źle napisana funkcjahashCode()
może zwrócić wartość skrótu, która będzie zbyt wysoka lub zbyt niska. Aby rozwiązać ten problem, wprowadzono innąhash()
funkcję i przekazano do niej wartość skrótu obiektu, aby wartość skrótu odpowiadała rozmiarowi tablicy. - Teraz wywoływana jest funkcja, która
indexFor(hash, table.length)
oblicza dokładną pozycję, w której obiekt zostanie umieszczonyEntry
. - Tutaj zaczyna się główna część. Teraz, w oparciu o to, co wiemy, że dwa różne obiekty mogą mieć takie same kody mieszające, zadajemy pytanie: Czy dwa różne obiekty zostaną umieszczone w tej samej pozycji w tablicy [bucket]? Odpowiedź brzmi:
LinkedList
. Jeśli pamiętasz, klasaEntry
ma atrybut „next
”. Ten atrybut zawsze wskazuje następny obiekt w łańcuchu. To jest dokładnie takie zachowanieLinkedList
.
Entry
są przechowywane w formie LinkedList
. Kiedy obiekt Entry
ma zostać umieszczony w określonej lokalizacji, HashMap sprawdza, czy w tej lokalizacji znajduje się już wpis. Jeśli nie ma wpisu, obiekt zostaje umieszczony w tej pozycji. Jeśli jednak na tej pozycji znajduje się już obiekt, sprawdzany jest kolejny atrybut. Jeśli powróci null
i bieżący obiekt Entry
stanie się kolejnym łączem w pliku LinkedList
. Jeżeli następna zmienna nie jest null
, procedura jest powtarzana dla kolejnej, aż zostanie znaleziona null
. A co jeśli umieścimy kolejny obiekt o innej wartości, ale z tym samym kluczem co poprzednio? Logicznie rzecz biorąc, powinno to skutkować zastąpieniem starej wartości. Jak to się stało? Ogólnie rzecz biorąc, po ustaleniu położenia obiektu Entry
, podczas dochodzenia LinkedList
do obliczonej pozycji, HashMap
wywołuje metodę porównania klucza dla każdego obiektu Entry
. Wszystkie te Entry
obiekty LinkedList
mogą mieć podobne kody skrótu, ale metoda equals()
sprawdzi prawdziwe podobieństwo. Spowoduje to jedynie zastąpienie wartości w pliku Entry
. Tym samym HashMap gwarantuje unikalność wszystkich kluczy.
Jak działa metoda get() w Javie?
Teraz mamy pojęcie, w jaki sposób pary klucz-wartość są przechowywane w plikachHashMap
. Następne ważne pytanie brzmi: co się stanie, gdy obiekt zostanie przekazany z HashMap do metody get()
? Jak ustala się wartość przedmiotu? Odpowiedź powinniśmy już znać, gdyż sposób wyznaczania unikalności klucza w metodzie put()
ma tę samą logikę, którą stosuje metoda get()
. Po HashMap
ustaleniu klucza obiektu przekazanego jako argument po prostu zwraca wartość odpowiedniego obiektu Entry
. Jeśli nie zostaną znalezione żadne dopasowania, metoda get()
zwróci null
. Rzućmy okiem na kod:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<k,V>e=table[indexFor(hash,table.length)];e!=null;e=e.next){
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
Powyższy kod jest podobny do metody put()
stosowanej do tej pory if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
, a następnie zwraca po prostu wartość obiektu.
Notatki
- Struktura danych przechowywana w obiekcie
Entry
to tablica z nazwątable
i typemEntry
. - Każda pojedyncza pozycja w tablicy nazywana jest wiadrem, ponieważ może zawierać pierwszy element
LinkedList
obiektówEntry
. hashCode()
Klucz potrzebny jest do obliczenia pozycji obiektuEntry
.equals()
Klucz służy do sprawdzania unikalności klucza na mapie(map
).hashCode()
iequals()
Wartości nie są używane w metodachget()
iset()
wHashMap
.- Kod skrótu dla kluczy z wartością
null
wynosi zawsze 0. I taki obiektEntry
będzie zawsze przechowywany w pozycji zerowej tablicy.
GO TO FULL VERSION