Middle
Как е структурирана и организирана структурата на данните HashMap в Java?
sobes.tech AI
Отговор от AI
HashMap в Java се основава на принципите на хеширането. Той съхранява двойки "ключ-стойност".
Вътрешна структура:
- Масиви от кофи (контейнери). Всяка кофичка е свързан списък (или дърво, от Java 8 нататък, при голям брой колизии).
- При добавяне на елемент (
put):- Изчислява се хеш кодът на ключа (
key.hashCode()). - Хеш кодът се модифицира за по-добро разпределение (
hash). - Използвайки модифицирания хеш и размера на масива, се изчислява индексът на кофата (
hash & (array_size - 1)). - Елементът (двойката "ключ-стойност" като обект
Node) се поставя в тази кофа. Ако кофата вече съдържа елементи, новият елемент се добавя в началото на свързания списък или дървото. - При добавяне се проверява наличието на ключа: използва се методът
equals()за сравнение на ключовете в кофата. Ако ключът е намерен, стойността се актуализира.
- Изчислява се хеш кодът на ключа (
- При получаване на елемент (
get):- Индексът на кофата се изчислява по ключа.
- В кофата се търси елемент по ключ, използвайки методите
hashCode()иequals(). - Връща се свързаната стойност.
Организация:
- Колизии: Ако няколко ключа имат еднакъв хеш код и попадат в една и съща кофа, елементите се съхраняват като свързан списък. От Java 8 нататък, ако броят на елементите в кофата надвиши прага (обикновено 8), свързаният списък се преобразува в дърво за по-бързо търсене (O(log n) вместо O(n)).
- Преоразмеряване: Когато броят на елементите надвиши "порога на зареждане" (
load factor * capacity), HashMap увеличава размера на вътрешния масив (обикновено двойно) и пре-хешира всички елементи. Това е скъпа операция (O(n)). - Параметри:
capacity: Начален размер на масива (по подразбиране 16).load factor: Порога на зареждане (по подразбиране 0.75). Определя кога ще се извърши преоразмеряване.
Защо hashCode() и equals() са важни:
- Правилната работа на HashMap зависи от правилната реализация на тези методи.
- Ако
equals()връщаtrueза два обекта,hashCode()трябва да връща същата стойност. - Неправилната реализация може да доведе до това, че елементите няма да бъдат намерени (
getще върнеnull), дори ако са добавени.
Пример за структура Node:
// Само за вътрешна употреба
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
HashMap осигурява бързо (средно O(1)) добавяне, получаване и изтриване на елементи при равномерно разпределение на хешовете. В най-лошия случай (силни колизии) операцията може да стане O(n) или O(log n) с дървета.