Как устроена и работает HashMap в Java?

Ответ

HashMap<K, V> — это реализация интерфейса Map, основанная на хеш-таблице. Она хранит пары «ключ-значение» и обеспечивает константное время выполнения O(1) для основных операций (get, put) в среднем случае.

Принцип работы put(key, value):

  1. Вычисляется хеш-код ключа с помощью метода key.hashCode().
  2. На основе хеш-кода вычисляется индекс корзины (bucket) в массиве: index = (n - 1) & hash, где n — текущая емкость массива.
  3. Если корзина по этому индексу пуста — в нее помещается новый узел (Node<K,V>).
  4. Если корзина уже содержит элементы (коллизия):
    • В Java 7 и ранее элементы связывались в односвязный список.
    • В Java 8+, если список становится длиннее порога (обычно 8), он преобразуется в сбалансированное бинарное дерево (TreeNode) для восстановления производительности до O(log n).

Пример:

HashMap<String, Integer> map = new HashMap<>();
map.put("apple", 10); // 1. hash("apple"), 2. вычисление индекса, 3. сохранение
Integer count = map.get("apple"); // Аналогичный расчет для быстрого поиска

Важные характеристики и настройки:

  • Начальная емкость (capacity): По умолчанию 16. Размер внутреннего массива корзин.
  • Коэффициент загрузки (load factor): По умолчанию 0.75. При достижении size > capacity * loadFactor происходит rehashing — увеличение емкости вдвое и перераспределение всех элементов.
  • Порядок: Не гарантирует порядок элементов. Для сохранения порядка вставки используйте LinkedHashMap.
  • Допустимые значения: Позволяет один ключ null и множество значений null.
  • Потокобезопасность: HashMap не является потокобезопасной. Для многопоточных сценариев используйте ConcurrentHashMap или синхронизацию через Collections.synchronizedMap().

Ответ 18+ 🔞

Смотри, вот эта вся штука HashMap<K, V> — это, по сути, такая хитрая жопа, которая умеет хранить пары «ключ-значение». Основана на хеш-таблице, да. Главный её фокус в том, что в среднем она работает за константное время, то есть O(1), для основных операций — достать (get) и положить (put). Неплохо, да?

Как работает этот цирк с методом put(key, value):

  1. Сначала она у ключа выпытывает хеш-код, вызывая key.hashCode(). Ну, типа, получает его цифровой отпечаток.
  2. Потом на основе этого хеша вычисляет, в какую корзину (bucket) запихнуть значение. Формула простая: index = (n - 1) & hash, где n — это размер внутреннего массива. Всё, чтобы равномерно размазать.
  3. Если корзина по этому индексу пустая — ура, просто суёшь туда новый узел (Node<K,V>) и идёшь пить чай.
  4. А вот если корзина уже занята (это называется коллизия, все её боятся), то начинается магия:
    • В старых добрых Java 7 и раньше элементы в корзине просто связывались в односвязный список. Как очередь в поликлинике, только без талонов.
    • А в Java 8 и новее, если этот список становится слишком длинным (обычно больше 8 элементов), то он волшебным образом превращается в сбалансированное бинарное дерево (TreeNode). Это чтобы производительность не проседала в пизду, а оставалась на уровне O(log n). Умно, блядь!

Вот тебе живой пример, чтобы не быть мудаком:

HashMap<String, Integer> map = new HashMap<>();
map.put("apple", 10); // 1. Считаем hash("apple"), 2. Находим корзину, 3. Кидаем туда яблоко
Integer count = map.get("apple"); // Ищется так же быстро — по хешу и индексу. Вжух, и готово!

А теперь про важные фишки и подводные ебли:

  • Начальная ёмкость (capacity): По умолчанию 16. Это размер внутреннего массива корзин. Можно задать свою, если знаешь, сколько элементов будет.
  • Коэффициент загрузки (load factor): По умолчанию 0.75. Это такой стоп-кран. Когда количество элементов превышает ёмкость * коэффициент, происходит rehashing — ёмкость массива удваивается, и все элементы переезжают на новые места. Операция, блядь, дорогая, так что имей в виду.
  • Порядок элементов: Его нет, он не гарантирован. Если тебе важен порядок, в котором ты всё туда пихал, то тебе нужен LinkedHashMap. Этот хоть запоминает.
  • Про null: Можно положить один ключ null и сколько угодно значений null. Мелочь, а приятно.
  • Потокобезопасность: А вот её тут нет, ебать! HashMap сам по себе — не для многопоточных игр. Если будешь из нескольких потоков в него писать, получишь race condition и прочие весёлые глюки. Для таких дел бери ConcurrentHashMap или оборачивай через Collections.synchronizedMap(). Иначе — сам дурак.