put方法执行逻辑
put方法是HashMap的核心。其基本流程如下:
- 判断键值对数组table是否为空或长度为0,如果是,则进行初始化。
- 计算key的hash值,确定在数组中的索引位置。
- 如果该索引处没有元素,直接插入。
- 如果该索引处有元素(发生哈希冲突),检查key是否相同:
- 如果key相同,直接替换value。
- 如果key不同,判断当前节点是否为TreeNode,如果是,调用树插入方法;否则,遍历链表。
- 插入链表后,如果链表长度大于8,且数组长度大于64,则将链表转换为红黑树。
- 检查当前元素数量是否超过阈值(threshold),如果超过,则进行扩容。
// 简化版put逻辑伪代码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
// 1. 初始化或扩容
// 2. 计算索引
// 3. 处理冲突 (链表或树)
// 4. 计数与扩容检查
}
get方法执行逻辑
get方法相对简单,主要目的是根据key找到对应的value:
- 计算key的hash值。
- 定位到数组中的桶位置。
- 如果桶的第一个节点key匹配,直接返回value。
- 如果第一个节点不匹配:
- 如果是红黑树,调用树的getTree方法。
- 如果是链表,遍历链表,直到找到匹配的key或遍历结束。
由于红黑树的引入,get操作在最坏情况下的时间复杂度从O(n)优化到了O(log n)。
resize扩容源码解析
JDK 1.8的扩容算法是一个亮点。它不需要重新计算哈希,而是利用扩容后容量是原来的2倍这一特性:
- 原索引位置上的节点,如果在扩容后哈希值的最高位bit为0,则仍留在原位置。
- 如果最高位bit为1,则移动到“原索引 + 旧容量”的位置。
这种设计极大地提高了扩容效率,避免了大量的哈希重算。