Ответ 1
A HashMap организован как массив "ведер" на основе хэш-кода вставленных элементов. Каждое ведро (по умолчанию) содержит связанный список элементов. У каждого ведра будет очень мало элементов (в идеале, не более одного), так что для нахождения определенного элемента требуется очень мало поиска по связанному списку.
Чтобы взять простой пример, скажем, у нас есть HashMap с емкостью 4 и коэффициент загрузки 0,75 (по умолчанию), что означает, что он может удерживать до 3 элементов до изменения размера. Идеальное распределение элементов в ведра будет выглядеть примерно так:
bucket | elements
-------+---------
0 | Z
1 | X
2 |
3 | Y
поэтому любой элемент можно найти сразу без поиска в ведре. С другой стороны, очень плохое распределение элементов будет выглядеть так:
bucket | elements
-------+---------
0 |
1 | Z -> X -> Y
2 |
3 |
Это произойдет, если все элементы произойдут с хэшем в одно и то же ведро, поэтому поиск элемента Y потребует прохождения по связанным спискам.
Это может показаться не очень важным, но если у вас есть HashMap с емкостью 10 000 элементов и в объединенном списке содержится 7500 элементов, поиск определенного элемента будет деградировать до линейного времени поиска - - это то, что пытается избежать HashMap.
Одна из проблем заключается в том, что хэш-код для распределения элементов в ведра определяется самими объектами, а реализация хеш-кода объектов не всегда очень хороша. Если hashCode не очень хорош, тогда элементы могут группироваться в определенных ведрах, а HashMap начнет плохо работать.
Комментарий от кода говорит о вероятности появления разных длин связанных списков в каждом ковше. Во-первых, предполагается, что хэш-коды распределены случайным образом - это не всегда так! - и я думаю, что он также предполагает, что количество элементов в HashMap составляет 50% от количества ведер. Согласно этим предположениям, согласно распределению Пуассона, 60,6% ведер будет пустым, 30,3% будут иметь один элемент, 7,5% будут иметь два элемента, 1,2% - три элемента и т.д.
Другими словами, учитывая эти (идеальные) предположения, связанные списки в каждом ведре обычно будут очень короткими.
В JDK 8 существует оптимизация для превращения связанного списка в дерево выше определенного порогового размера, так что по меньшей мере производительность ухудшается до O (log n) вместо O (n) в худшем случае. Вопрос в том, какое значение следует выбирать в качестве порога? Это то, о чем эта дискуссия. Текущее пороговое значение TREEIFY_THRESHOLD равно 8. Опять же, при этих идеальных предположениях ведро со связанным списком длины 8 будет иметь место только 0.000006% времени. Поэтому, если мы получим связанный список, который долгое время, что-то явно не идеально! Это может означать, например, что хранящиеся объекты имеют исключительно плохие хэш-коды, поэтому HashMap должен переключиться со связанного списка на дерево, чтобы избежать чрезмерной деградации производительности.
Ссылка на исходный файл с комментарием находится здесь:
http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/jdk8-b119/src/share/classes/java/util/HashMap.java