Как Java 8 HashMap вырождается в сбалансированные деревья, когда многие ключи имеют один и тот же хэш-код? - программирование

Как Java 8 HashMap вырождается в сбалансированные деревья, когда многие ключи имеют один и тот же хэш-код?

Как Java 8 HashMap вырождается в сбалансированные деревья, когда многие ключи имеют один и тот же хэш-код? Я прочитал, что ключи должны реализовать Comparable, чтобы определить порядок. Как HashMap сочетает хеширование и естественный порядок реализации деревьев? Что относительно классов, которые не реализуют Comparable, или когда несколько, не взаимно сопоставимых реализаций Comparable являются ключами на одной карте?

4b9b3361

Ответ 1

Комментарий примечания по реализации в HashMap - лучшее описание операции HashMap, чем я мог написать сам. Соответствующие части для понимания узлов дерева и их упорядочения:

Эта карта обычно выступает как хэш-таблица с биндером (bucketed), но когда ячейки становятся слишком большими, они преобразуются в бункеры TreeNodes, каждый из которых структурирован аналогично таковым в java.util.TreeMap. [...] Буферы TreeNodes могут проходить и использоваться как любые другие, но дополнительно поддерживать быстрый поиск при перенаселении. [...]

Ячейки деревьев (т.е. бины, чьи элементы представляют собой все TreeNodes) упорядочиваются в основном с помощью hashCode, но в случае связей, если два элемента имеют один и тот же тип класса C реализует Comparable, тогда их метод compareTo используется для упорядочения, (Мы консервативно проверяем общие типы посредством отражения, чтобы проверить это - см. Метод compareClassFor). Добавленная сложность древовидных дворов имеет смысл при обеспечении операций O (log n) в худшем случае, когда ключи либо имеют разные хэши, либо упорядочиваются. Таким образом, производительность изящно снижается при случайных или злонамеренных использованиях, в которых методы hashCode() возвращают значения, которые плохо распределенные, а также те, в которых многие ключи используют хэш-код, если они также являются сопоставимыми. (Если ни одно из них не применимо, мы можем потерять около двух раз во времени и пространстве по сравнению с принятием каких-либо мер предосторожности. Но единственные известные случаи связаны с плохими практиками программирования пользователей, которые уже настолько медленны, что это мало чем отличается.)

Если два объекта имеют одинаковые хеш-коды, но не взаимно сопоставимы, метод tieBreakOrder вызывается для разрыва связи, сначала путем сравнения строк на getClass().getName() (!), то, сравнивая System.identityHashCode.

Фактическое построение дерева начинается в treeifyBin, начиная с того момента, когда бит достигает TREEIFY_THRESHOLD ( в настоящее время 8), если хэш-таблица имеет как минимум MIN_TREEIFY_CAPACITY емкость (в настоящее время 64). Это обычная реализация красно-черного дерева (CLR кредитования), с некоторыми осложнениями для поддержки обхода так же, как и хэш-бины (например, removeTreeNode).

Ответ 2

Прочитайте code. Это главным образом красно-черное дерево.

Это фактически не требует реализации Comparable, но может использовать его, если доступно (см., например, метод )

Ответ 3

HashMap имеет собственный хэш-метод, который применяет дополнительный 2-х битный хэш к объектам внутри, чтобы избежать этих проблем:

Применяет дополнительную хэш-функцию к заданному хэш-коду, который защищает от хеш-функций низкого качества. Это важно, потому что HashMap использует хэш-таблицы с длиной в две строки, которые в противном случае сталкиваются с конфликтами для хэш-кодов, которые не отличаются в младших битах. Примечание. Нулевые ключи всегда отображают хэш 0, таким образом, индекс 0.

Если вы хотите посмотреть, как это сделать, посмотрите в источник класса HashMap.

static int hash(int h) {
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}