← Назад к вопросам
Сразу ли поместятся элементы в бакет после сравнения hashCode
2.8 Senior🔥 251 комментариев
#Коллекции
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
HashMap: Процесс добавления элемента
Вопрос: Сразу ли поместятся элементы в бакет после сравнения hashCode?
Ответ: Нет, не сразу. HashMap использует двухуровневый процесс: сначала hashCode, потом equals.
Подробный процесс добавления в HashMap
map.put(key, value) процесс:
1. Вычисляем hash(key.hashCode())
2. Вычисляем индекс bucket-a
3. Идём в этот bucket
4. Ищем элемент с таким же key используя equals()
- Если найден: заменяем value (UPDATE)
- Если не найден: добавляем новый элемент (INSERT)
5. Проверяем load factor и делаем resize если нужно
Шаг за шагом
public class HashMapAddExample {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
// Шаг 1: Вычисляем hashCode
String key1 = "apple";
int hash = key1.hashCode(); // Например: -1424385949
// Шаг 2: Вычисляем индекс bucket-a (capacity = 16)
int index = hash & (16 - 1); // 15 (0b1111)
// Результат: индекс 15 (в bucket-e 15)
// Шаг 3: Добавляем в bucket 15
map.put(key1, 100); // На хеше 15
// Шаг 4: Добавляем другой ключ с тем же хешем
String key2 = "banana";
int hash2 = key2.hashCode(); // Другой хеш
int index2 = hash2 & (16 - 1); // Может быть тот же индекс
if (index2 == index) {
// Коллизия! Оба в одном bucket-е
// Но equals() проверит что они разные
map.put(key2, 200);
}
}
}
Процесс с примером коллизии
Добавляем три элемента:
1. put("apple", 100)
hashCode() → 2345
bucket = 2345 % 16 = 5
Bucket 5: apple → 100
2. put("banana", 200)
hashCode() → 1234
bucket = 1234 % 16 = 2
Bucket 2: banana → 200
3. put("apricot", 300)
hashCode() → 2354 (похож на apple, но другой)
bucket = 2354 % 16 = 5
❌ КОЛЛИЗИЯ! Тот же bucket 5
Проверяем equals():
"apricot".equals("apple") → false
Добавляем оба в bucket 5:
Bucket 5: [apple → 100, apricot → 300]
Внутренняя структура (Java 8+)
До Java 8: Linked List в bucket-е при коллизии
Bucket 5: apple → 100 → apricot → 300 → avocado → 400
Java 8+: Red-Black Tree при коллизии (если > 8 элементов)
Bucket 5: Binary Tree Structure
/ \
apple apricot
/ \ / \
(null) (null)(null) avocado
Правильный процесс добавления
public class HashMapPutProcess {
public static void main(String[] args) {
HashMap<User, String> map = new HashMap<>();
User user1 = new User("john@example.com", "John");
User user2 = new User("john@example.com", "Jane"); // Другое имя!
// Первое добавление
map.put(user1, "Value 1");
System.out.println("Map size: 1, value: Value 1");
// Второе добавление (тот же hashCode и equals)
map.put(user2, "Value 2");
// Результат:
System.out.println("Map size: " + map.size()); // 1 (не 2!)
System.out.println("Value: " + map.get(user1)); // "Value 2" (переписалось)
System.out.println("Value: " + map.get(user2)); // "Value 2" (одно и то же)
}
}
class User {
String email;
String name;
User(String email, String name) {
this.email = email;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof User)) return false;
User user = (User) o;
return email.equals(user.email); // Сравниваем только email!
}
@Override
public int hashCode() {
return email.hashCode();
}
}
Блок-схема процесса
map.put(key, value)
↓
Вычисли hash = hash(key.hashCode())
↓
Вычисли bucket = hash % capacity
↓
Подойди к bucket-у
↓
┌───────────────────────────────────┐
│ Ищем node с key в bucket-е │
│ используя equals() │
└───────────────────────────────────┘
↓
ДА ←─ Найден node.key.equals(key)?
│
├→ Обновляем value
│ node.value = value
│
↓ НЕТ
│
├→ Добавляем новый node
│ node.next = head
│ head = node
│ size++
│
↓
Проверяем load factor
if (size > capacity * loadFactor) {
resize()
}
↓
Завершено
Пример с коллизией и разными ключами
public class CollisionHandling {
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<>();
// Два элемента с одинаковым hashCode
// (Для примера, обычно это не происходит)
Integer key1 = 16; // hashCode = 16
Integer key2 = 32; // hashCode = 32
map.put(key1, "Sixteen");
map.put(key2, "ThirtyTwo");
// Разные ключи → разные bucket-ы
System.out.println(map.get(16)); // "Sixteen"
System.out.println(map.get(32)); // "ThirtyTwo"
System.out.println(map.size()); // 2
// Теперь добавим ключ 16 еще раз
map.put(16, "Sixteen Updated");
System.out.println(map.size()); // Все еще 2 (обновили, не добавили)
}
}
Resize процесс
Когда size > capacity * loadFactor (по умолчанию 0.75):
1. Создаём новый массив большего размера (capacity * 2)
2. Перехешируем ВСЕ элементы (пересчитываем индексы)
3. Переносим элементы в новые bucket-ы
Зачем пересчитывать:
Особых индекс зависит от capacity:
bucket_index = hash & (capacity - 1)
Если capacity удвоится:
bucket_index = hash & (capacity*2 - 1)
Результат может быть другой!
На собеседовании
Правильный ответ:
"HashMap использует двухшаговый процесс:
- hashCode() вычисляет индекс bucket-а
- Находим bucket и внутри ищем элемент используя equals()
Если элемент не найден (по equals()) — добавляем его, даже если hashCode совпадает.
Если элемент найден (equals() = true) — обновляем value.
При коллизии (несколько ключей в одном bucket-е) используется:
- До Java 8: LinkedList
- Java 8+: Tree если > 8 элементов
Алгоритм гарантирует что каждый ключ появляется ровно один раз в Map (или обновляется если добавить снова)."
Ключевые выводы
- hashCode() определяет bucket
- equals() проверяет наличие в bucket-е
- Оба нужны для корректной работы
- Коллизии нормальны (разные ключи, один bucket)
- Red-Black Tree при > 8 элементов в bucket-е
- Resize удваивает capacity при load factor превышении
- Процесс O(1) в среднем, O(log n) в худшем (дерево)