Map
Карта — это структура данных, которая часто используется в процессе разработки.Key-value
Абстракция карты ключ-значениеинтерфейс, карта не содержит повторяющихся ключей, т.е. одному ключу соответствует одно значение.HashMap
,HashTable
,ConcurrentHashMap
Все они являются важными членами Java Collection Framework. Интерфейс карты предоставляет три вида коллекций, которые позволяют просматривать содержимое карты в виде набора ключей (keySet()), набора значений (values()) или набора сопоставлений ключ-значение (entrySet()).
Хеш-таблица
Мы знаем, что метод хранения массива заключается в выделении фиксированного непрерывного пространства в памяти, скорость адресации высокая (скорость запроса высокая), а временная сложность равнаO(1)
, но элементы массива нужно перемещать при вставке и удалении элементов, поэтому скорость вставки и удаления низкая, а временная сложностьO(n)
. Способ хранения связанного списка разрывный в памяти, каждый элемент хранит в памяти адрес следующего элемента, и следующий элемент находится по этому адресу, поэтому связанный список работает медленно при запросе, а временная сложность составляетO(n)
, это быстро при вставке и удалении, а временная сложностьO(1)
.
Если нам нужна структура данных, которая бы одновременно быстро запрашивала и быстро вставляла и удаляла, что нам делать? В настоящее времяХеш-таблицародился в свое время, черезхэш-функцияРассчитатьключМесто хранения, указанное в хеш-таблице (обратите внимание, что место хранения здесь — это место в таблице, а не адрес памяти), называется хеш-адресом, и затем значение сохраняется по этому хэш-адресу, а затем передаетсяключможет работать непосредственно наценность, временная сложность запроса, вставки, удаления и других операций составляетO(1)
.
Поскольку ключ вычисляет место хранения с помощью хеш-функции, качество хеш-функции напрямую влияет на эффективность работы хеш-таблицы, например, неиспользуемое пространство для хранения и большое количество конфликтов (т. е. места хранения, рассчитанные по разным ключам). одинаковые)).
Хэш-функция может отображать ввод произвольной длины в вывод фиксированной длины, который является хэш-адресом. Коллизии хешей неизбежны, и есть два часто используемых решения для конфликтов хэшей.
-
Метод цепного адреса (метод застежки-молнии)
Используя метод объединения массива и связанного списка, для каждого хеш-адреса в хэш-таблице создается линейная таблица, в линейной таблице сохраняются данные с одинаковым хеш-адресом, аУказатель заголовка связанного списка хранится в массиве, хеш-адрес, ключ, значение и другая информация обычно хранятся в узле связанного списка. Как правило, нижний индекс массива вычисляется по хеш-адресу, и одно и то же значение хеш-функции сохраняется в массиве с тем же самым индексом. Метод zip подходит для частых операций вставки и удаления. -
открытая адресация
Метод открытой адресации также называется методом линейного обнаружения. Основная идея заключается в следующем: рассматривайте хэш-таблицу T[0...m-1] как круговой вектор. Если начальный адрес обнаружения равен d, самый длинный путь обнаружения: д, д+г, д+2г, ..., м-1. То есть, начиная с адреса d при обнаружении, сначала обнаружьте T[d], если T[d] имеет коллизию хэшей, продолжайте обнаруживать следующий T[d+1]... пока не будет обнаружен T[m-1]. , i — пользовательская константа. Открытая адресация легко генерируетсяагломерация, так называемое явление кластеризации заключается в том, что данные в хеш-таблице связаны друг с другом, и при добавлении новых элементов могут возникать коллизии хэшей. -
Сравнение метода zipper и открытой адресации
Метод молнии: легко справляться с конфликтами, без явления накопления и простой вставки и удаления связанных списков, поэтому метод молнии подходит для частых операций вставки и удаления.
Метод открытой адресации: чтобы уменьшить конфликты, коэффициент загрузки (коэффициент заполнения) должен быть небольшим, и при большом масштабе узла будет потрачено много места. А когда открытый метод адресации удаляет узел, пространство, в котором находится узел, нельзя просто сделать пустым, иначе путь поиска узла после него будет усечен, так как в различных открытых методах адресации,Пустые адресные блоки — все условия для неудачного поиска.. Поэтому при удалении узла необходимо использовать логическое удаление, то есть пометить удаляемый узел на удаление.
Коэффициент загрузки = количество элементов, заполненных в хеш-таблице / длина массива хэш-таблицы
HashMap
структура данных
HashMap
используя вышеизложенноемолния методУстранение конфликтов хэшей. HashMap не является потокобезопасным, позволяя использовать ключи, значенияnull
, не гарантирует порядок (например, порядок вставки) и не гарантирует, что порядок не изменится с течением времени (данные будут перенесены после того, как хеш-таблица удвоится и расширится).
Давайте создадим HashMap и запустим его
HashMap<String, Integer> map = new HashMap();
map.put("语文", 1);
map.put("数学", 2);
map.put("英语", 3);
map.put("历史", 4);
map.put("政治", 5);
map.put("地理", 6);
map.put("生物", 7);
map.put("化学", 8);
Из рисунка видно, что HashMap хранится не в порядке вставки (неупорядоченно).Далее мы смотрим на HashMapструктура данных HashMap имеет несколько важных переменных-членов,
table
,size
,threshold
,loadFactor
,modCount
.
- таблица: это
Entry[]
тип массива, аEntry
на самом делеОдносвязный список, пары ключ-значение хеш-таблицы хранятся вEntry
массив, каждыйEntry
Соответствует хеш-адресу, гдеEntry
так называемое ведро - size: размер HashMap, то есть количество сохраненных пар ключ-значение.
- DEFAULT_INITIAL_CAPACITY: емкость HashMap по умолчанию (размер массива) по умолчанию равна 16.
- MAXIMUM_CAPACITY: максимальная емкость HashMap (30 из 2), если входящая емкость больше этого значения, она будет заменена максимальной емкостью.
- порог: это порог HashMap, который используется для определения необходимости корректировки емкости HashMap. порог=емкость*коэффициент загрузки, когда количество пар ключ-значение, хранящихся в HashMap, достигает порога, HashMapдвойнойрасширение
- loadFactor: коэффициент нагрузки
- modCount: используется для реализацииотказоустойчивый механизмиз
Fail Fast Механизм: Дляпоток небезопасен(Обратите внимание, что этот механизм доступен только для небезопасных для потоков коллекций.) Итераторы объектов коллекции. Если другие потоки изменяют структуру или количество элементов объекта коллекции во время использования итератора, итерация немедленно завершается, и итератор бросать
ConcurrentModificationException
.
Конструктор
HashMap имеет 4 конструктора, а именно:
//无参构造函数,负载因子为默认的0.75,HashMap的容量(数组大小)默认容量为16
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
//指定HashMap容量大小的构造函数 负载因子为默认的0.75
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//指定HashMap容量大小和负载因子的构造函数
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
//包含子Map的构造函数,负载因子为默认的0.75
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
Почему коэффициент загрузки по умолчанию равен 0,75? Согласно официальному объяснению, при коэффициенте загрузки 0,75,
Entry
Длину односвязного списка практически невозможно превысить.8
(Вероятность достижения 8 равна 0,00000006), эффект состоит в том, чтобы позволитьEntry
Длина односвязного списка должна быть как можно меньше, чтобы сделать эффективность запросов HashMap максимально возможной.
Так как размер HashMap (то есть size) больше начальной емкости (capacity), то HashMap будет увеличен в два раза, так как во многих случаях не нужно так сильно расширяться, когда мы знаем размер наших данных, мы можем использовать его в HashMap.Укажите емкость (размер массива) при инициализации.
Обратите внимание, что емкость, которую мы указываем, должна бытьмощность 2, даже если емкость, которую мы передаем, не является степенью числа 2, исходный код преобразует емкость в степень числа 2. Например, если мы передаем 5, окончательная емкость будет равна 8.
Почему емкость должна быть степенью двойки? Поскольку HashMap представляет собой структуру массива + односвязный список, мы надеемся, что элементы хранятся более равномерно, а идеальное состояние состоит в том, что каждый
Entry
В нем хранится только один элемент, который наиболее эффективен при выполнении запросов. Как его хранить равномерно? Первое, о чем мы думаем, это хэш-адрес операции по модулю % емкости.У мастеров SUN та же идея, что и у нас, но они используют битовую операцию для достижения этой операции (битовая операция эффективна), чтобы сделать битовую операцию и получить Результат операции по модулю тот же, т.е.hash & (capacity - 1) == hash % capacity
, размер емкости (Capacity) должен быть степенью числа 2.
положить метод
До JDK1.8 вставка hashMap вставлялась в начало связанного списка.В этой статье анализируется исходный код JDK1.8, который вставляется в конец связанного списка.
- По ключу (ключу)
hashCode()
Вычислить текущую пару ключ-значениехэш-адрес, используемый для поиска нижнего индекса пары ключ-значение, хранящейся в массиве HashMap. - судить
table
Инициализировать ли, вызовите, если не инициализированresize()
заtable
Инициализируйте емкость и значение порога - В соответствии с **длиной массива таблицы и хэш-адресом выполните и операцию (i = (n - 1) и хеш)** для вычисления соответствующего ключа
table
Индекс массива, если соответствующая позиция индекса массива не имеет значения, вызовитеnewNode(hash, key, value, null)
метод для создания узла для этой пары ключ-значение.
Вот вопрос для размышления: при изменении длины табличного массива получается неверное значение? Анализ сдается позже. Вот краткий анализ того, почему индекс массива напрямую не основан на хеш-адресе, а используетсяДлина массива таблицы и хэш-адрес делают и выполняют операцию (i = (n - 1) и хеш)(Поскольку размер массива является степенью числа 2, эта операция эквивалентна операции изменения размера массива) Вычислить индекс массива, поскольку хеш-адрес может превышать размер массива, и сделать ключ- пары значений распределяются более равномерно В каждом сегменте (связанном списке), поскольку емкость будет меняться, хэш-адреса узлов в каждом сегменте (связанный список) не будут одинаковыми, и один и тот же хэш-адрес может также быть присвоен разным индексам.
- Если ключ, соответствующий ключу, вычисляется по хеш-адресу
table
Индекс массива имеет узлы, а ключи узловkey
и входящий ключkey
равны, хэш-адрес и входящий хэш-адрес также равны,затем назначьте соответствующую ссылку узла для e. - Если ключ, соответствующий ключу, вычисляется по хеш-адресу
table
Индекс массива имеет узлы, и хеш-адрес узла совпадает с входящим хэш-адресом, но ключ узлаkey
и входящий ключkey
Если не равно, пройти по связанному списку, если в процессе обхода найден ключ узлаkey
и входящий ключkey
равны, хэш-адрес и входящий хеш-адрес также равны, то соответствующийvalue
обновление стоимости. В противном случае позвонитеnewNode(hash, key, value, null)
метод для создания узла для пары ключ-значение и добавления его в связанный списокхвостик, если длина связанного списка после добавления узла >= 8, он будет преобразован в красно-черное дерево - если e не пусто и
onlyIfAbsent
заtrue
не будет перезаписывать то же самоеkey
и тот же хэш-адресvalue
.
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//如果参数onlyIfAbsent是true,那么不会覆盖相同key的值value。如果evict是false。那么表示是在初始化时调用的
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//tab存放 当前的哈希桶, p用作临时链表节点
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果当前哈希表是空的,代表是初始化
if ((tab = table) == null || (n = tab.length) == 0)
//那么直接去扩容哈希表,并且将扩容后的哈希桶长度赋值给n
n = (tab = resize()).length;
//如果当前index的节点是空的,表示没有发生哈希碰撞。 直接构建一个新节点Node,挂载在index处即可。
//这里再啰嗦一下,数组下标index 是利用 哈希地址 & 哈希桶的长度-1,替代模运算
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {//否则 发生了哈希冲突。
//e
Node<K,V> e; K k;
//如果哈希值相等,key也相等,则是覆盖value操作
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;//将当前节点引用赋值给e
else if (p instanceof TreeNode)//红黑树暂且不谈
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//不是覆盖操作,则插入一个普通链表节点
//遍历链表
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {//遍历到尾部,追加新节点到尾部
p.next = newNode(hash, key, value, null);
//如果追加节点后,链表数量》=8,则转化为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果找到了要覆盖的节点
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//如果e不是null,说明有需要覆盖的节点,
if (e != null) { // existing mapping for key
//则覆盖节点值,并返回原oldValue
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//这是一个空实现的函数,用作LinkedHashMap重写使用。
afterNodeAccess(e);
return oldValue;
}
}
//如果执行到了这里,说明插入了一个新的节点,所以会修改modCount,以及返回null。
//修改modCount
++modCount;
//更新size,并判断是否需要扩容。
if (++size > threshold)
resize();
//这是一个空实现的函数,用作LinkedHashMap重写使用。
afterNodeInsertion(evict);
return null;
}
hashCode() — это метод класса Object.Метод hashCode() возвращает хэш-код объекта.Этот метод предназначен для лучшей поддержки хэш-таблиц, таких как Set, HashTable, HashMap и т. д. Роль hashCode(): если вы используете equals для сравнения, если есть 1000 элементов, и вы создаете новый элемент, вам нужно вызвать equals 1000 раз, чтобы сравнить их один за другим, чтобы увидеть, являются ли они одним и тем же объектом, который сильно снизит эффективность. ashcode фактически является адресом хранения возвращаемого объекта.Если в этой позиции нет элемента, элемент сохраняется непосредственно на ней.Если в этой позиции есть элемент, в это время вызывается метод equal для сравнения с новым элемент. Если он такой же, то его не будет. Сохраняется, хэшируется на другой адрес.
получить метод
-
table
не пусто иtable
имеет длину больше 0, и согласно ключуkey
изhashCode()
Рассчитатьхэш-адрес,Опять такиВыполнение и операция на основе количества сегментов - 1 и хеш-адресаВычислить индекс массива, если индекс не пустой (т.е. есть указатель на голову связанного списка), продолжить спуск вниз, иначе вернутьnull
. - Если и хэш-адрес, ключ первого узла
key
совпадают, верните первый узел. - Если следующий узел первого узла не пуст, продолжить, если первый узел является узлом дерева, выполнить
getTreeNode(hash, key)
, найти узел в дереве и вернуться. В противном случае пройдите по связанному списку и найдите ключkey
, если хеш-адрес тот же, будет возвращен этот узел.
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // 如果索引到的第一个Node,key 和 hash值都和传递进来的参数相等,则返回该Node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) { //如果索引到的第一个Node 不符合要求,循环变量它的下一个节点。
if (first instanceof TreeNode) // 在树中get
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {// 在链表中get
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
метод удаления
-
table
не пусто иtable
имеет длину больше 0, и согласно ключуkey
изhashCode()
Рассчитатьхэш-адрес, а затем вычислить индекс массива по хеш-адресу.Если индекс не пустой (т.е. есть указатель на голову связанного списка), продолжаем идти вниз, иначе выполняем 6`. - Если хэш-адрес, ключ
key
Такой же,Затем назначьте соответствующую ссылку узла узлу, затем перейдите к 4. В противном случае перейдите к 3. - Если это дерево, выполнить
getTreeNode(hash, key)
Найдите узел в дереве и верните его, в противном случае перейдите по связанному списку, чтобы найти ключkey
, узлы с одинаковым хэш-адресом, а затемНазначьте соответствующую ссылку узла узлу, затем перейдите к 4, в противном случае перейдите к 6. - если узел
node
Не пустой (т.е. ключ запрашиваетсяkey
соответствующий узел), и когдаmatchValue
заfalse
когда илиvalue
Если они также равны, выполните 5, иначе выполните 6. - Если узел является деревом, вызовите
removeTreeNode(this, tab, movable)
Удалите соответствующий узел. В противном случае удалите соответствующий узел из связанного списка, - вернуть
null
.
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
// p 是待删除节点的前置节点
Node<K,V>[] tab; Node<K,V> p; int n, index;
//如果哈希表不为空,则根据hash值算出的index下 有节点的话。
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
//node是待删除节点
Node<K,V> node = null, e; K k; V v;
//如果链表头的就是需要删除的节点
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;//将待删除节点引用赋给node
else if ((e = p.next) != null) {//否则循环遍历 找到待删除节点,赋值给node
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
//如果有待删除节点node, 且 matchValue为false,或者值也相等
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)//如果node == p,说明是链表头是待删除节点
tab[index] = node.next;
else//否则待删除节点在表中间
p.next = node.next;
++modCount;//修改modCount
--size;//修改size
afterNodeRemoval(node);//LinkedHashMap回调函数
return node;
}
}
return null;
}
метод containsKey
если указанный ключ существуетkey
, возвращает true, иначе возвращает false.
Метод, вызываемый методом containsKey, аналогичен методу, вызываемому методом get. См. анализ метода get.
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
Инициализация хэш-таблицы и метод изменения размера двойного расширения
Анализируя метод изменения размера, мы можем понять, почему после изменения емкости хэш-таблицы можно получить правильное значение.
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//如果哈希表是空的 则将旧容量置为0,否则置为旧哈希表的容量
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//旧的哈希表的阈值
int oldThr = threshold;
//新的哈希表的容量和阈值 都置为0
int newCap, newThr = 0;
//如果旧的容量大于0 即不是第一次初始化 是扩容操作
if (oldCap > 0) {
//旧的容量是否大于2的30次幂方(容量的最大值)
if (oldCap >= MAXIMUM_CAPACITY) {
//阈值设置为Integer的最大值
threshold = Integer.MAX_VALUE;
//返回旧的哈希表(旧的哈希表已经到最大的容量了,不能继续扩容 所以返回)
return oldTab;
}
//新的哈希表容量的=旧的容量<<1,即新的容量=旧的2倍,如果新的容量小于2的30次幂方(容量的最大值) 且 旧的容量大于等于默认的容量(16)
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//新的哈希表的阈值=旧的哈希表的阈值<<1,既即新的阈值=旧的2倍 扩容table
newThr = oldThr << 1; // double threshold
}
//第一次初始化,如果旧的阈值>0
即HashMap是以传入容量大小或者传入容量大小、负载因子的构造函数进行初始化的,阈值thr
eshlod已经在构造函数初始化过了,所以阈值在这里大于0
else if (oldThr > 0) // initial capacity was placed in threshold
//新的容量=旧的阈值
newCap = oldThr;
//如果是以无参构造函数进行初始化的,则
新的容量大小=默认的容量大小,新的阈值=默认的负载因子*默认的容量大小
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//新的阈值=0,即执行的是上面的else if (oldThr >
0)(使用带参数的构造函数初始化),是使用带参数的构造函数进行的初始化,并且计算出新的
阈值
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//创建新的哈希表,容量为新的容量
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//重点看这部分,如果旧的哈希表不为空
if (oldTab != null) {
//遍历旧的哈希表,
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//如果旧的哈希表的节点不为空
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
//如果节点没有下个节点了(即只有一个节点),则直接放到新的哈希表中
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
//将旧的哈希表的节点全部重新定位,比如旧的哈希表容量是16,有一个
值a放在数组下标为0上,现在新的哈希表容量是32,重新定位后值a就被重
新定位到下标为32上,即新的哈希表的下标为32储存值a,简单来说就是新
的下标=旧的哈希表的下标+新的哈希表的容量,正是因为这个节点的迁移,
所以我们在hashMapputget操作的时候,在哈希表容量变化后仍让取到正确
的值,但是也因为这个迁移操作,会消耗很多资源,所以尽量在创建HashMa
p的时候就估计哈希表的容量,尽量不要让他加倍扩容。这里的迁移也都是
运用的位运算,所以在初始化的时候,桶的数量必须是2幂次方,才能保证
位运算和取模运算结果一样。
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
Мы можем запустить пример и отладить его.
HashMap<String, Integer> map = new HashMap();
for (int i = 1; i <= 24; i ++) {
map.put(String.valueOf(i), i);
}
for (int i = 25; i <= 80; i ++) {
map.put(String.valueOf(i), i);
}
Мы используем конструктор без аргументов (то есть емкость хеш-таблицы по умолчанию равна 16, а коэффициент загрузки по умолчанию равен 0,75).new
HashMap, затем отладьте, чтобы увидеть
for
петля, см.11
Сохраненный индекс равен 0,12
Сохраненный индекс 1Продолжить выполнение второго
for
, обнаружил, что нижний индекс 0 становится 44, а нижний индекс 1 становится 45Так где же хранятся наши 11 и 12? Можно обнаружить, что 11 и 12 стоят на индексах 32 и 33, то есть при втором исполнении
for
Когда хеш-таблица расширяется, а затем переносятся узлы, новый индекс = старый индекс + емкость новой хэш-таблицы.
использованная литература
Принцип работы и реализация Java HashMap
Обзор карты (1): полное понимание HashMap
Оригинальный адрес: https://ddnd.cn/2019/03/07/jdk1.8-hashmap/