HashMap серии Java Collection
Всем привет, друзья, которые любят читать мои статьи, должны знать, что автор пишет цикл статей по параллельному программированию на Java, но сегодня я буду вставлять некоторые статьи в серию сборников по Java, и продолжу писать статьи по параллельное программирование на Java и Java параллельно.Сборник статей циклом, потому что писать цикл статей постоянно очень скучно. И эти две серии на самом деле связаны между собой, надеюсь, вам понравится. Что ж, без лишних слов, сегодня я расскажу вам о базовой реализации HashMap Структура выглядит следующим образом:
- Базовая структура данных HashMap
- Основной API
- Взаимное расширение (мощность, коэффициент загрузки, кратный 2)
- Не потокобезопасный
1. Базовая структура данных HashMap
Нижний слой HashMap представляет собой структуру данных массива плюс связанный список.Каждый элемент массива ведет к связанному списку через указатель.Хвостовой указатель связанного списка указывает на NULL.Структура этого элемента следующая :static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
....
}
Как видите, K и V сохраняются, а затем следующий указатель указывает на следующий элемент.
2. Основной API
Знать структуру хранения недостаточно, как элементы попадают в эту коллекцию? Давайте взглянем на основной API, поставим и получим.
положить () объяснение:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
//key为null时,直接丢到table[0]这个数组位置上
return putForNullKey(value);
//自己封装的hash方法,对key做完hash后加了高位运算,尽量避免Hash碰撞
int hash = hash(key);
//根据hash值`对数组长度取模`,算出应该放入索引为几的数组格子里
int i = indexFor(hash, table.length);
//如果这个格子里已经有元素了,那么就遍历执行equals方法,如果equals也一样,就用value替换掉oldValue。返回oldValue
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//如果这个数组隔离里没有equals一样的,把这个元素加入到链表的头部。
modCount++;
addEntry(hash, key, value, i);
return null;
}
Когда мы помещаем элемент в HashMap, сначала пересчитываем хеш-значение в соответствии с хэш-кодом ключа и получаем позицию (т.е. индекс) этого элемента в массиве в соответствии с хэш-значением.Если в нем уже хранятся другие элементы позицию массива, то элементы в этой позиции будут храниться в виде связанного списка, вновь добавленные элементы помещаются в начало цепочки, а первые добавленные элементы помещаются в конец цепочки. Если в этой позиции в массиве нет элемента, элемент непосредственно помещается в эту позицию в этом массиве. Также видно, что когда пара ключ-значение,Не учитывает значение в Entry вообще, просто по ключу для расчета и определения места хранения каждой записи. Мы можем полностью рассматривать значение в наборе карт как дочернюю часть ключа.Когда система определяет место хранения ключа, значение(может быть NULL), а затем сохраните его там.
Вот, кстати, почему длина массива HashMap должна быть кратна 2 (по умолчанию 16) при расширении, на самом деле это связано с методом indexFor(), который вычисляет соответствующий индекс в массиве по хеш-значение ключа, вы можете подумать, что прямое использование длины массива hash% может гарантировать, что элементы будут распределены в массиве как можно более равномерно, чтобы избежать меньшего количества коллизий? Однако эффективность взятия по модулю относительно невысока, поэтому реализация этого API выглядит следующим образом:
static int indexFor(int h, int length) {
return h & (length-1);
}
Чтобы иметь тот же эффект, что и h%length, h&(length-1) должен гарантировать, что длина длины кратна 2. А почему, младший брат не знает, таков алгоритм. Символ & гораздо более эффективен, чем символ %. Так все знают. Почему длина массива должна быть кратна 2. В целом, когда длина массива равна n-й степени числа 2, вероятность того, что разные ключи будут вычислены с одинаковым индексом, относительно мала, тогда данные распределяются по массиву равномерно, т. е. вероятность коллизий невелико Относительно запроса Когда нет необходимости обхода связанного списка в определенной позиции, эффективность запроса будет выше.
Подводя итог put() немного:Когда программа пытается поместить пару ключ-значение в HashMap, программа сначала определяет место хранения Entry в соответствии с возвращаемым значением hashCode() ключа: если возвращаемое значение hashCode() Ключи двух записей одинаковы, то и место их хранения одинаковое. Если ключи этих двух Записей возвращают значение true при сравнении на равенство, значение вновь добавленной Записи перезапишет значение исходной Записи в коллекции, а ключ — нет. Если ключи этих двух Записей возвращают false при сравнении на равенство, вновь добавленная Запись образует цепочку Записей с исходной Записью в коллекции, а вновь добавленная Запись находится во главе цепочки Записей..
получить () объяснение:
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
На самом деле каждый должен понимать это сам, это относительно просто, получая элемент из HashMap, сначала вычислить hashCode ключа, найти элемент в соответствующей позиции в массиве, а затем использовать равенство метод ключа, чтобы найти потребность в связанном списке соответствующей позиции.Элементы.
Хорошо, сводка по хранению и чтению элементов:Проще говоря, HashMap обрабатывает ключ-значение как единое целое внизу, и это целое является объектом Entry. Нижний слой HashMap использует массив Entry[] для хранения всех пар ключ-значение.Когда объект Entry необходимо сохранить, его место хранения в массиве будет определено в соответствии с алгоритмом хеширования, а его местоположение в массиве будет определяется по методу equals.Место хранения в связанном списке, когда Запись нужно вынуть, она также найдет место хранения в массиве по алгоритму хеширования, а затем вынет Запись из связанного список в этом месте в соответствии с методом equals.
3. Связано с расширением мощностей (мощность, коэффициент загрузки, кратный 2)
Когда элементов в HashMap становится все больше и больше, вероятность коллизии хэшей все выше и выше, потому что длина массива по умолчанию равна 16. Поэтому для повышения эффективности запроса необходимо расширить массив HashMap.Операция расширения массива появится и в ArrayList.Это обычная операция.После расширения массива HashMap наибольшая производительность появляется точка потребления:Данные в исходном массиве должны пересчитать свое положение в новом массиве и вставить его, что является изменением размера.
Итак, когда HashMap расширится? Когда количество элементов в HashMap превышает (размер массива XloadFactor), массив будет расширен.Значение loadFactor по умолчанию равно 0,75, что является компромиссным значением. То есть по умолчанию размер массива равен 16, затем, когда количество элементов в HashMap превышает 16X0,75=12, размер массива расширяется до 2X16=32, то есть удваивается (подумайте о том, что я говорил ранее о кратности 2), а затем пересчитать позицию каждого элемента в массиве, что является очень затратной операцией, поэтому, если мы уже знаем количество элементов в HashMap, предустановленное количество элементов может быть эффективным Улучшить производительность HashMap.
Далее, кстати, упоминаются несколько методов построения HashMap:
- HashMap(): создает HashMap с начальной емкостью 16 и коэффициентом загрузки 0,75.
- HashMap(int initialCapacity): создает HashMap с начальной емкостью initialCapacity и коэффициентом загрузки 0,75.
- HashMap(int initialCapacity, float loadFactor): создайте HashMap с указанной начальной емкостью и указанным коэффициентом загрузки.
Чем больше коэффициент загрузки, тем полнее используется пространство, но следствием этого является снижение эффективности поиска; если коэффициент загрузки слишком мал, данные в хеш-таблице будут слишком разреженными, что приведет к серьезной трате пространства.. Значение по умолчанию 0,75 на самом деле является компромиссом.
4. Не потокобезопасный
Чтобы упомянуть немного, HashMap не является потокобезопасным, Когда используется многопоточный одновременный доступ, расширение может привести к тому, что связанный список после элемента массива образует замкнутый цикл, поэтому запрос всегда находится в бесконечном цикле, получается 100% утилизация. Поэтому ConcurrentHashMap следует использовать вместо этого в многопоточной среде. ConcurrentHashMap объясню позже.Кроме того, почему HashMap вызывает такой бесконечный цикл, ведь статей в интернете больше, не буду лишним, и приведу несколько порталов:
- Бесконечный цикл, вызванный HashMap при высокой степени параллелизма
- Неправильное использование HashMap приводит к 100% расследованию проблем с ЦП
Эпилог
Что ж, HashMap закончен для всех, и я продолжу делиться с вами базовыми знаниями о коллекциях Java в будущем.Кончено, хорошего дня!