Рубрики
Без рубрики

Коэффициент загрузки хэш-карты Java

Автор оригинала: baeldung.

1. Обзор

В этой статье мы рассмотрим значение коэффициента загрузки в Java HashMap и то, как он влияет на производительность карты.

2. Что такое HashMap?

Класс HashMap принадлежит к структуре коллекции Java и обеспечивает базовую реализацию интерфейса Map . Мы можем использовать его, когда хотим хранить данные в терминах пар ключ-значение. Эти пары ключ-значение называются записями карты и представлены классом Map.Entry .

3. Внутренние компоненты HashMap

Прежде чем обсуждать коэффициент нагрузки, давайте рассмотрим несколько терминов:

    • хеширование
    • вместимость
    • порог
    • перефразирование
    • столкновение

HashMap работает по принципу хеширования — алгоритма сопоставления данных объекта с некоторым репрезентативным целочисленным значением . Функция хэширования применяется к ключевому объекту для вычисления индекса корзины для хранения и извлечения любой пары ключ-значение.

Емкость-это количество ведер в HashMap . Начальная емкость-это емкость на момент создания M ap . Наконец, начальная емкость HashMap по умолчанию равна 16.

По мере увеличения количества элементов в HashMap емкость расширяется. Коэффициент нагрузки-это мера, которая решает, когда следует увеличить пропускную способность Карты . Коэффициент загрузки по умолчанию составляет 75% от емкости.

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

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

Давайте создадим нашу HashMap :

Map mapWithDefaultParams = new HashMap<>();
mapWithDefaultParams.put("1", "one");
mapWithDefaultParams.put("2", "two");
mapWithDefaultParams.put("3", "three");
mapWithDefaultParams.put("4", "four");

Вот структура нашей карты :

Как мы видим, наша HashMap была создана с начальной емкостью по умолчанию (16) и коэффициентом загрузки по умолчанию (0,75). Кроме того, пороговое значение равно 16 *, что означает, что оно увеличит емкость с 16 до 32 после добавления 12-й записи (пары ключ-значение).

4. Настраиваемая Начальная емкость и коэффициент нагрузки

В предыдущем разделе мы создали нашу HashMap с помощью конструктора по умолчанию. В следующих разделах мы рассмотрим, как создать HashMap , передав конструктору начальную емкость и коэффициент загрузки.

4.1. С Начальной Мощностью

Во-первых, давайте создадим Карту с начальной емкостью:

Map mapWithInitialCapacity = new HashMap<>(5);

Он создаст пустую карту с начальной емкостью (5) и коэффициентом загрузки по умолчанию (0,75).

4.2. С Начальной мощностью и коэффициентом нагрузки

Аналогично, мы можем создать нашу карту , используя как начальную емкость, так и коэффициент загрузки:

Map mapWithInitialCapacityAndLF = new HashMap<>(5, 0.5f);

Здесь он создаст пустую карту с начальной емкостью 5 и коэффициентом загрузки 0,5.

5. Производительность

Хотя у нас есть гибкость в выборе начальной мощности и коэффициента нагрузки, мы должны выбирать их с умом. Оба они влияют на производительность Map . Давайте углубимся в то, как эти параметры связаны с производительностью.

5.1. Сложность

Как мы знаем, HashMap внутренне использует хэш-код в качестве основы для хранения пар ключ-значение. Если метод hashCode() хорошо написан, HashMap распределит элементы по всем корзинам. Поэтому HashMap хранит и извлекает записи в постоянное время O(1) .

Однако проблема возникает, когда количество элементов увеличивается, а размер ведра фиксируется. В каждом ведре будет больше предметов, и это будет мешать временной сложности.

Решение заключается в том, что мы можем увеличить количество ведер, когда количество элементов увеличивается. Затем мы можем перераспределить элементы по всем ведрам. Таким образом, мы сможем сохранить постоянное количество элементов в каждом ведре и поддерживать временную сложность O(1) .

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

HashMap обычно имеет пространственную сложность O(n) , где n – количество записей. Более высокое значение коэффициента загрузки уменьшает накладные расходы на пространство, но увеличивает стоимость поиска .

5.2. Пересказ

Когда количество элементов в Карте пересекает пороговое значение, емкость Карты удваивается. Как обсуждалось ранее, при увеличении емкости нам необходимо равномерно распределить все записи (включая существующие записи и новые записи) по всем сегментам. Здесь нам нужно перефразировать. То есть для каждой существующей пары ключ-значение снова вычислите хэш-код с увеличенной емкостью в качестве параметра.

В принципе, когда коэффициент нагрузки увеличивается, сложность возрастает. Перефразирование выполняется для поддержания низкого коэффициента нагрузки и низкой сложности всех операций.

Давайте инициализируем нашу Карту :

Map mapWithInitialCapacityAndLF = new HashMap<>(5,0.75f);
mapWithInitialCapacityAndLF.put("1", "one");
mapWithInitialCapacityAndLF.put("2", "two");
mapWithInitialCapacityAndLF.put("3", "three");
mapWithInitialCapacityAndLF.put("4", "four");
mapWithInitialCapacityAndLF.put("5", "five");

И давайте взглянем на структуру карты :

Теперь давайте добавим больше записей на нашу Карту :

mapWithInitialCapacityAndLF.put("6", "Six");
mapWithInitialCapacityAndLF.put("7", "Seven");
//.. more entries
mapWithInitialCapacityAndLF.put("15", "fifteen");

И давайте еще раз понаблюдаем за нашей Картой структурой:

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

5.3. Столкновение

Коллизии могут возникать из-за плохого алгоритма хэш-кода и часто замедляют производительность карты .

До Java 8 HashMap в Java обрабатывает коллизии с помощью LinkedList для хранения записей карты. Если ключ оказывается в том же ведре, где уже существует другая запись, он добавляется в начало связанного списка /. В худшем случае это увеличит сложность до O(n) .

Чтобы избежать этой проблемы, Java 8 и более поздние версии используют сбалансированное дерево (также называемое красно-черным деревом ) вместо LinkedList для хранения конфликтующих записей. Это улучшает наихудшую производительность HashMap от O(n) до O(log n) .

HashMap изначально использует Связанный список. Затем, когда количество записей превысит определенный порог, он заменит LinkedList сбалансированным двоичным деревом. Константа TREEIFY_THRESHOLD определяет это пороговое значение. В настоящее время это значение равно 8, что означает, что если в одном ведре находится более 8 элементов, Map будет использовать дерево для их хранения.

6. Заключение

В этой статье мы обсудили одну из самых популярных структур данных: HashMap . Мы также видели, как фактор нагрузки вместе с емкостью влияет на его производительность.

Как всегда, примеры кода для этой статьи доступны на GitHub .