Как изменяется структура HashMap в Java при добавлении большого числа элементов?

«Как изменяется структура HashMap в Java при добавлении большого числа элементов?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

HashMap динамически адаптирует свою внутреннюю структуру для поддержания эффективности операций put() и get().

Ключевые преобразования:

  1. Увеличение массива (Resize / Rehashing): При достижении порога (capacity * loadFactor, по умолчанию 0.75) размер массива бакетов увеличивается вдвое. Все существующие элементы перераспределяются по новым бакетам на основе их хэш-кода и нового размера массива. Это дорогая операция O(n).
  2. Преобразование цепочек в деревья (Java 8+): Каждый бакет изначально хранит элементы как связный список. Если длина цепочки в одном бакете превышает TREEIFY_THRESHOLD (8) и общее количество бакетов превышает MIN_TREEIFY_CAPACITY (64), список преобразуется в красно-черное дерево. Это снижает худший случай сложности с O(n) до O(log n) при коллизиях хэшей.

Пример, провоцирующий преобразования:

HashMap<Key, Integer> map = new HashMap<>();
// Предположим, у всех ключей плохой хэш-код, попадающий в один бакет
for (int i = 0; i < 20; i++) {
    map.put(new BadHashKey(i), i); // Может привести к treeify
}

Почему это важно:

  • Resize поддерживает низкий коэффициент заполнения, уменьшая коллизии.
  • Деревья защищают от атак, основанных на искусственном создании коллизий хэш-кодов, которые могли бы деградировать производительность до O(n).
  • Для ключей всегда правильно переопределяйте hashCode() и equals().