HashMap, сложность не в Map, а в Hash

Java

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

Обычно основной целью использования Map является поставить (положить), получить доступ (получить) или удалить (удалить), и особых требований к порядку нет — HashMap в данном случае лучший выбор.

01. Хэш

Для HashMap трудно понять не Map, а Hash.

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

Чтобы быть более интуитивным, нужно смешать строку данных wang и вывести другую er данных фиксированной длины в качестве функции data wang. Обычно мы используем цепочку отпечатков пальцев для отображения определенного человека. Не стоит недооценивать отпечаток пальца размером с ваш палец. алгоритм тоже очень мощный, у вас есть?).

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

В Java хеш-значение строки String вычисляется следующим образом:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

Неважно, понимаете вы или нет, мы относимся к этому как к алгоритму «умножение-сложение итеративной операции». Пользуясь случаем, давайте взглянем на хэш-значения четырех строк «Shen», «Mo», «Wang» и «two».

String[] cmower = { "沉", "默", "王", "二" };
for (String s : cmower) {
	System.out.println(s.hashCode());
}

Вывод выглядит следующим образом (5 цифр):

Хэш-значение Шена: 27785 Хэш-значение по умолчанию: 40664. Королевский хэш: 29579 Хэш из двух: 20108

Для HashMap назначение Hash (ключ, позиция ключа) - ускорить поиск пар ключ-значение (думаете, если телефонная книга не упорядочена по первой букве имени человека, как это было бы сложно чтобы найти человека «Мои друзья в WeChat». Многие из них добавили букву А перед своим ником, так жестоко»). Обычно мы привыкли использовать String в качестве ключа карты, см. следующий код:

Map<String, String> map = new HashMap<>();
String[] cmower = { "沉", "默", "王", "二" };
for (String s : cmower) {
	map.put(s, s + "月入25万");
}

Будет ли HashMap действительно использовать String в качестве фактического ключа? Давайте посмотрим на исходный код метода put HashMap:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

Хотя только одинputVal()вызов метода, но вы должны были обнаружить, что ключ будет выполнять хеш-операцию внутри HashMap, конкретный код выглядит следующим образом:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

Если ключ представляет собой строку String,hash()Сначала получается hashCode (хэш-значение) строки, затем находится хэш-значение, а окончательное значение является фактическим ключом (значение int) HashMap.

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

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

02. Как разрешать конфликты хеш-значений

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

То есть ключ1 ≠ ключ2, но функция(ключ1) может быть равна функции(ключ2) — значения хеша сталкиваются. что делать?

Самое простое решение, чтобы подумать: когда значение Hahh Value Key2 Key2 конфликтает с значением HASH Value1, другое значение HASH значение1 генерируется на основе значения. Если значение1 и значение больше не конфликтует, то используйте значение1 в качестве хеша Значение Key2.

Таким образом, всегда будет найден тот, который не конфликтует.

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

Существует три основных метода построения HashMap:

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}


public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}


public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
}

где initialCapacity — начальная мощность (1

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

Как правило, коэффициент загрузки по умолчанию (0,75) представляет собой компромисс между затратами времени и места. Высокий коэффициент загрузки снижает объем пространства, но в то же время увеличивает стоимость запросов. Если коэффициент загрузки слишком мал, следует увеличить начальную мощность, иначе будет происходить частое увеличение мощности.

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

Если вы можете заранее предсказать количество пар ключ-значение, к которым нужно получить доступ, вы можете рассмотреть возможность установки соответствующей начальной емкости (больше, чем «оценочное количество элементов/коэффициент загрузки», и является степенью 2).

04. Резюме

Долгое время мои знания о HashMap ограничивались его использованием.put(key, value)иvalue = get(key).

Но, когда я заставил себя каждую неделю выводить техническую статью по Java, я действительно "понял" HashMap - значение хэша (hash value), коллизия хешей (коллизия хэшей), начальная емкость и коэффициент загрузки, на самом деле может стоять перед меня и смеяться все время - и изначально, я убежал, когда я увидел эти ключевые слова, я боялся их видеть.