Короткий ответ
put(key, value) сначала вычисляет хэш ключа и индекс корзины, затем ищет в ней узел с равным ключом: если такой ключ уже есть — значение заменяется на новое, а старое возвращается; если нет — в корзину добавляется новый узел. Высчитывать позицию бакета нужно, чтобы не перебирать все записи: ключ сразу приводит к месту хранения, поэтому в среднем операции занимают O(1).
Как это работает подробнее
Индекс вычисляется как (n - 1) & hash. Если корзина пуста, в неё просто помещается новый узел. Если занята, узлы проверяются: сначала сверяется хэш, затем ключи через equals(). При совпадении значение заменяется, при несовпадении узел добавляется в цепочку корзины; в Java 8+, если цепочка превысила порог, она превращается в дерево, и вставка идёт по его правилам. После вставки проверяется порог ёмкость × load factor — при превышении выполняется resize с пересчётом позиций. Метод get работает симметрично: корзина по хэшу, затем сравнение ключей через equals.
- Зачем бакет: адресация по хэшу избавляет от линейного прохода по единому списку, иначе каждый поиск был бы
O(n). - Преимущество: при равномерном распределении ключей время доступа почти постоянно и не зависит от размера карты.
Пример кода
Map<String, Integer> map = new HashMap<>();
Integer old = map.put("one", 1); // ключа нет — добавляется узел, old == null
Integer prev = map.put("one", 2); // ключ найден — значение заменено, prev == 1
System.out.println(map.get("one")); // 2Что использовать на практике
- Для ключей с корректными
equals/hashCodeHashMap даёт среднееO(1)на put/get — это основной сценарий. - Следите, чтобы хэш-функция ключа распределяла значения равномерно, иначе цепочки растут и скорость падает.
- Если нужен порядок ключей или диапазонные выборки — вместо HashMap берите TreeMap.
Подводные камни
- Неизменяемость ключа важна: если после вставки изменить поля, влияющие на
hashCode,getбудет искать элемент в другой корзине и «не найдёт» его. - Слишком высокий load factor экономит память, но увеличивает число коллизий и вероятность дорогого resize.
putвозвращает старое значение, а не признак успеха: для проверки «был ли ключ» используйтеcontainsKeyили сверяйте с null.
Как отвечать на собеседовании
- Начните с того, что put сначала вычисляет hashCode ключа и индекс корзины по формуле (n - 1) & hash → опишите поиск в корзине: сверка hash и equals, замена значения или добавление узла → упомяните порог 8 и превращение цепочки в красно-чёрное дерево → добавьте проверку ёмкость × load factor и resize → объясните преимущество бакета: прямой доступ без перебора, в среднем O(1) вместо O(n).