TGViewer
Java: fill the gaps Java: fill the gaps @java_fillthegaps · 12.3K subscribers
Post #478 14.8K
Java 20: новый костыль в HashMap

На вопрос выше логично ответить new HashMap<>(1000). Сначала расскажу, почему этот ответ не походит. Потом наглядно покажу нарушение инкапсуляции, и что за костыль добавили в Java 20.

Итак, что происходит внутри HashMap?

Ячейки хэш-таблицы (или бакеты) хранятся в переменной Node[] table как простой массив.

При вызове new HashMap() без параметров создаётся 16 бакетов. Если передать параметр с размером, то берётся ближайшая степень двойки.

new HashMap(1000) создаёт массив из 1024 элементов.

Кажется, что всё в порядке, но мы забыли про ребалансировку😒

HashMap хорошо работает, когда в каждом бакете 0 или 1 элемент. Тогда скорость поиска и добавления будет той самой О(1).

Когда элементов становится больше, растёт шанс, что в один бакет попадёт несколько элементов. Поэтому в определённый момент HashMap удваивает количество бакетов и перераспределяет элементы. За момент, когда пора начать эту операцию, отвечает поле threshold.

Его первое значение считается как [планируемое число элементов * 0.75], т.е когда HashMap заполнен на 3/4. При удвоении числа бакетов threshold тоже удваивается.

И смотрите, что получается:

🔹 Мы хотим добавить в мэп 1000 элементов и вызываем конструктор с подходящим параметром initialCapacity:
new HashMap(1000);

🔹 Создаётся 1024 бакета (ближайшая степень двойки)
🔹 Рассчитывается threshold: 1024*0,75 = 768
🔹 Добавляется 768 элементов
🔹 Приходит 769 элемент, начинается ребалансировка:
▫️ количество бакетов удваивается, теперь их 2048
▫️ текущие элементы распределяются между ними
▫️ новый threshold удваивается, теперь это 1538

Что получилось: мы пообещали добавить в HashMap 1000 элементов. Сдержали обещание, но перестройка мэп всё равно произошла.

Чтобы HashMap работал оптимально, нужно учесть ребалансировку и передать в конструктор, например, 1500. Надо знать детали реализации, чтобы получить то, что хотим.

И это образцовое нарушение инкапсуляции🤌

В java 20 в HashMap добавили костыльный метод, который исправляет ситуацию:

HashMap.newHashMap(1000);

Внутри произойдёт вычисление 1000 / 0.75 = 1334, в итоге создаётся 2048 бакетов.

Почему это костыль? Потому что исходная проблема не решается. Жизнь пользователя не становится легче, ему нужно запомнить "чтобы задать размер мэп — не пользуйся конструктором, пользуйся специальным методом".

Хороший API — понятный, удобный и дружелюбный. Пользователю легко выбрать нужный метод, все параметры хорошо описаны в документации. Когнитивная нагрузка при использовании минимальна, нет подводных камней и обходных путей. Для собеседований сложно придумать вопрос с подвохом🙂

Важные заметки:

🔸 В JDK много образцового кода, и я рекомендую изучать исходники как можно чаще. HashMap — неприятное исключение

🔸 В ConcurrentHashMap всё хорошо. new ConcurrentHashMap(1000) сразу создаёт 2048 бакетов и не занимается лишними балансировками
  • 🔥 207
  • 👍 77
  • ❤ 13
  • 👎 11
More from @java_fillthegaps
  1. Apr 30, 2026Get Your Hands Dirty on Clean Architecture: отзыв на книгу Когда я подняла тему чистой арх…
  2. Apr 27, 2026​Clean Architecture: отзыв на книгу Наконец-то дочитала книгу Clean Architecture Роберта М…
  3. Mar 4, 2026Как переиспользовать контекст в интеграционных тестах Сегодня расскажу базовый минимум для…
  4. Mar 4, 2026Post #660
  5. Mar 4, 2026Тестовый контекст поднимается 2 минуты. У нас 4 класса с интеграционными тестами, их конфи…
  6. Feb 25, 2026Чистая архитектура. Главы 3-5 Продолжаем спидран по Clean Architecture Роберта Мартина. ⭐️…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →