← Назад к вопросам
Будет ли работать HashMap, если все добавляемые ключи будут иметь одинаковый hashCode()?
2.2 Middle🔥 191 комментариев
#Коллекции#ООП
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
HashMap с одинаковыми hashCode всех ключей
Да, HashMap будет работать корректно даже если все ключи имеют одинаковый hashCode. Однако производительность деградирует до O(n) вместо ожидаемого O(1).
Практический пример
public class BadKey {
private String value;
public BadKey(String value) {
this.value = value;
}
@Override
public int hashCode() {
return 42; // Все объекты возвращают одинаковый хеш
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
BadKey other = (BadKey) obj;
return value.equals(other.value);
}
}
// Использование:
HashMap<BadKey, String> map = new HashMap<>();
map.put(new BadKey("key1"), "value1");
map.put(new BadKey("key2"), "value2");
map.put(new BadKey("key3"), "value3");
String result = map.get(new BadKey("key2")); // Работает, но медленнее
System.out.println(result); // value2
Внутренняя структура
Внутри HashMap все элементы попадут в одну ячейку массива и образуют длинную цепочку (linked list):
// Внутренняя структура HashMap:
// table[0]: Node -> Node -> Node -> Node
// table[1]: null
// table[2]: null
// ...
// table[15]: null
// При поиске HashMap проходит всю цепочку:
public V get(Object key) {
int hash = hash(key.hashCode());
Node<K,V> e = table[hash & (capacity - 1)]; // Все попадают в один индекс
// Проходит по всей цепочке O(n)
for (; e != null; e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
return e.value;
}
}
return null;
}
Почему это работает?
- hashCode возвращает только индекс в массиве, не определяет уникальность
- Реальное сравнение ключей проводится через equals()
- Коллизии разрешаются через цепочки — элементы хранятся в linked list
- HashMap проходит по цепочке и находит нужный элемент по equals()
// Поиск в цепочке:
for (; e != null; e = e.next) {
if (e.hash == hash && // Сначала сравнивает хеш
key.equals(e.key)) { // Потом сравнивает ключ через equals()
return e.value;
}
}
Производительность
// Тест производительности
HashMap<BadKey, Integer> map = new HashMap<>();
// Добавляем 100k элементов
for (int i = 0; i < 100_000; i++) {
map.put(new BadKey("key" + i), i);
}
// Поиск будет медленным
long start = System.nanoTime();
for (int i = 0; i < 10_000; i++) {
map.get(new BadKey("key" + i));
}
long duration = System.nanoTime() - start;
System.out.println("Время: " + duration / 1_000_000 + "ms");
// С нормальными хешами это займет ~5ms
// С одинаковыми хешами: ~500ms или больше
Java 8+ оптимизация
Начиная с Java 8, HashMap использует красно-чёрное дерево вместо linked list при слишком длинной цепочке:
// Когда цепочка достигает длины TREEIFY_THRESHOLD (8),
// LinkedList преобразуется в TreeMap
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
// Это улучшает производительность с O(n) до O(log n)
// Но все же медленнее чем O(1)
Правильная реализация hashCode
public class GoodKey {
private String value;
private int id;
@Override
public int hashCode() {
// Используй все значимые поля
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + value.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
GoodKey other = (GoodKey) obj;
return id == other.id && value.equals(other.value);
}
}
Вывод
- HashMap будет работать с одинаковыми hashCode
- Производительность деградирует до O(n) вместо O(1)
- Java 8+ использует TreeMap при длинных цепочках, улучшая до O(log n)
- Никогда не пишите hashCode() возвращающий константу без очень веской причины
- Плохой hashCode приводит к DoS атакам при обработке ненадежных данных