← Назад к вопросам

Сразу ли поместятся элементы в бакет после сравнения 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 использует двухшаговый процесс:

  1. hashCode() вычисляет индекс bucket-а
  2. Находим 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) в худшем (дерево)
Сразу ли поместятся элементы в бакет после сравнения hashCode | PrepBro