Полагаю, это трюк для оптимизации.
Вы правильно поняли. Приведенный вами конструктор использует коэффициент загрузки по умолчанию 0,75, поэтому для размещения initialCapacity
элементов размер хеш-таблицы должен быть не менее
initialCapacity / 0.75
(примерно столько же, сколько умножить на 1.3333333333). Однако деления с плавающей точкой стоят дорого (немного, не плохо). И нам дополнительно нужно округлить до целого числа. Я думаю, что целочисленное деление уже поможет
(initialCapacity * 4 + 2) / 3
(+ 2
предназначен для проверки округления результата; * 4
должен быть дешевым, поскольку его можно реализовать как сдвиг влево). Разработчики работают еще лучше: смены намного дешевле, чем подразделения.
initialCapacity + (initialCapacity >>> 1) + 1
Это действительно умножает на 1,5, поэтому дает нам результат, который часто будет больше, чем нужно, но это быстро. + 1
должен компенсировать тот факт, что «умножение» округлено в меньшую сторону.
Подробности: >>>
- беззнаковое смещение вправо, заполняющее ноль в крайнем левом положении. Уже зная, что initialCapacity
был неотрицательным, это дает тот же результат, что и деление на 2, игнорируя остаток.
Редактировать: я могу добавить, что tableSizeFor
округляется до степени 2, поэтому чаще всего одна и та же степень 2 будет конечным результатом, даже если первый расчет дал немного больший результат, чем необходимо. Например, если вы попросите емкость для 10 элементов (для простоты расчета), будет достаточно размера таблицы 14, где формула даст 16. Но 14 будет округлено до степени 2, так что мы все равно получим 16 так что в итоге разницы нет. Если бы вы попросили место для 12 элементов, размера 16 все равно было бы достаточно, но формула дает 19, которое затем округляется до 32. Это более необычный случай.
Дальнейшее редактирование: Спасибо за информацию в комментариях, которые вы отправили как ошибку JDK, и за предоставление ссылки: https://bugs.openjdk.java.net/browse/JDK-8202422. Первый комментарий Марин Буххольц согласен с вами:
Да, здесь есть ошибка. Конструктор с одним аргументом эффективно использует
коэффициент загрузки 2/3, а не задокументированное значение по умолчанию 3/4…
Я бы сам не считал это ошибкой, если бы вы не рассматривали ее как ошибку, из-за которой вы иногда получаете большую емкость, чем просили. С другой стороны, вы, конечно, правы (в вашем примерном кратком сообщении об ошибке), что есть несоответствие: вы ожидаете, что new ConcurrentHashMap(22)
и new ConcurrentHashMap(22, 0.75f, 1)
дадут тот же результат, так как последний просто дает задокументированный коэффициент загрузки по умолчанию / плотность стола; но размеры таблиц равны 64 для первого и 32 для второго.