← Назад к вопросам
Что такое коллизия HashCode с точки зрения HashMap?
2.0 Middle🔥 241 комментариев
#Коллекции#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Коллизии HashCode в HashMap
Коллизия (hash collision) — это ситуация, когда два разных объекта производят одинаковый хеш-код. Это нормальное явление в любой хеш-таблице и HashMap имеет механизмы для их разрешения.
Основы работы HashMap
HashMap использует массив "корзин" (buckets) фиксированного размера:
public class HashMap<K, V> {
// Массив корзин (обычно 16 по умолчанию)
private Node<K,V>[] table;
private int capacity; // размер массива
public V put(K key, V value) {
// 1. Вычисляем индекс: hashCode % capacity
int hash = hash(key.hashCode());
int index = hash & (capacity - 1); // эквивалент % capacity
// 2. Помещаем в корзину по индексу
table[index] = new Node(key, value);
}
public V get(Object key) {
int hash = hash(key.hashCode());
int index = hash & (capacity - 1);
// 3. Ищем в корзине
Node<K,V> node = table[index];
while(node != null) {
if(node.key.equals(key)) {
return node.value;
}
node = node.next; // проверяем следующие элементы
}
return null;
}
}
Что происходит при коллизии
Пример коллизии
Map<String, String> map = new HashMap<>();
// Примеры двух объектов с одинаковым hashCode
// (гипотетически, в реальности маловероятно)
public class Custom {
int value;
@Override
public int hashCode() {
return 42; // всегда одинаковый хеш
}
@Override
public boolean equals(Object o) {
return this == o;
}
}
Map<Custom, String> map = new HashMap<>();
Custom key1 = new Custom(1);
Custom key2 = new Custom(2);
map.put(key1, "first"); // hashCode = 42, index = 0
map.put(key2, "second"); // hashCode = 42, index = 0 — КОЛЛИЗИЯ!
Разрешение коллизии: Chaining (до Java 8)
В каждой корзине хранится цепочка элементов (linked list):
// Узел в HashMap
private static class Node<K,V> {
int hash;
K key;
V value;
Node<K,V> next; // указатель на следующий элемент
}
// При коллизии:
// table[0] -> Node(key1, "first") -> Node(key2, "second") -> null
// При поиске проходим по цепочке:
public V get(Object key) {
int index = hash(key) & (capacity - 1);
Node<K,V> node = table[index];
while(node != null) {
if(node.hash == hash && (key == node.key || key.equals(node.key))) {
return node.value;
}
node = node.next; // ← идём по цепочке при коллизии
}
return null;
}
Оптимизация: Tree-based buckets (Java 8+)
Если в корзине много элементов (обычно > 8), HashMap конвертирует цепочку в красно-чёрное дерево:
// Java 8+ оптимизация
private static final int TREEIFY_THRESHOLD = 8;
private static final int UNTREEIFY_THRESHOLD = 6;
// При добавлении 9-го элемента в корзину:
if(binCount >= TREEIFY_THRESHOLD - 1) {
treeifyBin(tab, hash); // преобразуем цепочку в дерево
}
// Это улучшает производительность при поиске:
// Цепочка: O(n) — нужно пройти все элементы
// Дерево: O(log n) — бинарный поиск
Визуализация
Без коллизии
index bucket
0 [key1 → value1]
1 [key2 → value2]
2 [null]
3 [key3 → value3]
Коллизия (цепочка)
index bucket
0 [key1 → value1] → [key4 → value4] → [key7 → value7] → null
1 [key2 → value2]
2 [null]
3 [key3 → value3]
Коллизия (дерево, Java 8+)
index bucket (8 элементов)
0 [root]
/ \
key1 key4
/ \ / \
key7 key2 key5 ...
Влияние на производительность
// Идеальный случай (без коллизий)
// Каждая корзина содержит ≤ 1 элемент
// get/put: O(1) в среднем
Map<Integer, String> map = new HashMap<>();
for(int i = 0; i < 1000; i++) {
map.put(i, "value" + i);
}
String value = map.get(500); // быстро
// Плохой случай (много коллизий)
public class BadHash {
@Override
public int hashCode() {
return 0; // все элементы в одной корзине!
}
}
Map<BadHash, String> map = new HashMap<>();
for(int i = 0; i < 1000; i++) {
map.put(new BadHash(), "value" + i);
}
// Все 1000 элементов в одной корзине
// get/put: O(n) — почти как LinkedList
Load Factor и rehashing
HashMap автоматически расширяется при заполнении:
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
public V put(K key, V value) {
// ... добавляем элемент
if(++size > capacity * loadFactor) {
resize(); // увеличиваем capacity в 2 раза
}
}
// При resize() все элементы перехешируются в новый массив
// Это уменьшает коллизии, но требует O(n) времени
Почему 0.75?
size / capacity = 0.75
// С вероятностной точки зрения это оптимум:
// - При меньшем коэффициенте: мало коллизий, но много памяти
// - При большем коэффициенте: экономия памяти, но много коллизий и rehashing
Правильная реализация hashCode/equals
public class Person {
private String name;
private int age;
// ❌ Плохо
@Override
public int hashCode() {
return 0; // все объекты имеют одинаковый хеш — много коллизий!
}
// ✅ Хорошо
@Override
public int hashCode() {
return Objects.hash(name, age); // использует поля
}
@Override
public boolean equals(Object o) {
if(this == o) return true;
if(!(o instanceof Person)) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
}
// ПРАВИЛО: если equals() говорит два объекта равны,
// то hashCode() должен вернуть одинаковый хеш!
// Но обратное не обязательно — hashCode может совпадать для разных объектов
Контракт hashCode/equals
// ✅ Правильно
Person p1 = new Person("John", 30);
Person p2 = new Person("John", 30);
if(p1.equals(p2)) { // true
assert p1.hashCode() == p2.hashCode(); // тоже true
}
Map<Person, String> map = new HashMap<>();
map.put(p1, "value1");
String found = map.get(p2); // найдёт, потому что equals() и hashCode() согласованы
assert found.equals("value1");
// ❌ Неправильно
public class BadPerson {
public boolean equals(Object o) {
return name.equals(((BadPerson)o).name);
}
public int hashCode() {
return age; // не основано на name!
}
}
// Два объекта с одинаковым name могут быть найдены неправильно
Заключение
- Коллизия — нормальное явление в хеш-таблицах
- Разрешение — цепочка элементов (до Java 8) или дерево (Java 8+)
- Производительность — O(1) в среднем, O(n) в худшем (плохой hashCode())
- Оптимизация — rehashing при load factor > 0.75
- Контракт — hashCode() и equals() должны быть согласованы
- Практика — используй Objects.hash() и Objects.equals()