推荐答案
HashMap 的底层实现原理
HashMap 是 Java 中最常用的数据结构之一,它基于哈希表实现。HashMap 的底层实现主要包括以下几个关键点:
数组 + 链表/红黑树:HashMap 的底层数据结构是一个数组,数组中的每个元素是一个链表或红黑树的头节点。当链表长度超过一定阈值时,链表会转换为红黑树,以提高查找效率。
哈希冲突的解决:当两个不同的键通过哈希函数计算得到相同的哈希值时,就会发生哈希冲突。HashMap 使用链地址法(Separate Chaining)来解决哈希冲突,即将哈希值相同的键值对存储在同一个链表中。
扩容机制:当 HashMap 中的元素数量超过负载因子(load factor)与当前容量的乘积时,HashMap 会进行扩容。扩容时,HashMap 会创建一个新的数组,并将原有数组中的元素重新哈希到新数组中。扩容后的容量通常是原容量的两倍。
本题详细解读
1. 数组 + 链表/红黑树
HashMap 的底层是一个 Node<K,V>[] 数组,每个 Node 是一个键值对。当插入一个键值对时,HashMap 会根据键的哈希值计算出数组的索引位置。如果该位置已经有元素存在(即发生了哈希冲突),则会将新元素插入到链表的末尾。当链表长度超过 8 时,链表会转换为红黑树,以提高查找效率。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
// ...
}2. 哈希冲突的解决
HashMap 使用链地址法来解决哈希冲突。具体来说,当两个键的哈希值相同时,它们会被存储在同一个链表中。在查找时,HashMap 会先通过哈希值定位到数组的索引位置,然后遍历链表或红黑树来找到对应的键值对。
// www.javascriptcn.com code example
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
// 处理哈希冲突
// ...
}
// ...
}3. 扩容机制
HashMap 的扩容机制是为了保持较低的负载因子,从而提高查找效率。当 HashMap 中的元素数量超过 capacity * loadFactor 时,HashMap 会进行扩容。扩容时,HashMap 会创建一个新的数组,并将原有数组中的元素重新哈希到新数组中。扩容后的容量通常是原容量的两倍。
// www.javascriptcn.com code example
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // 双倍扩容
}
// ...
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
// 重新哈希
// ...
return newTab;
}