Ответ
HashMap динамически адаптирует свою внутреннюю структуру для поддержания эффективности операций put() и get().
Ключевые преобразования:
- Увеличение массива (Resize / Rehashing): При достижении порога (
capacity * loadFactor, по умолчанию 0.75) размер массива бакетов увеличивается вдвое. Все существующие элементы перераспределяются по новым бакетам на основе их хэш-кода и нового размера массива. Это дорогая операцияO(n). - Преобразование цепочек в деревья (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().