1. 核心数据结构
JDK 1.7 及之前:数组 + 链表
JDK 1.8 及之后:数组 + 链表/红黑树(链表长度 ≥8 时转红黑树,≤6 时退化为链表)
// JDK 1.8 的 Node 定义(链表节点)
static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next; // 链表指针
}// TreeNode 定义(红黑树节点)
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent; // 父节点TreeNode<K,V> left; // 左子树TreeNode<K,V> right; // 右子树TreeNode<K,V> prev; // 前驱节点boolean red; // 颜色标识
}
2. 哈希函数设计
作用:将 Key 映射到数组索引,尽可能减少哈希冲突。
JDK 1.8 的优化:
static final int hash(Object key) {int h;// 高16位与低16位异或,提升散列性return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
索引计算:
index = (table.length - 1) & hash
-
长度取模优化:哈希表容量为 2 的幂次时,
(n-1) & hash
等效于hash % n
,但位运算更快。
3. put() 方法流程
-
计算哈希值:调用
hash(key)
。 -
初始化或扩容:若数组为空,调用
resize()
初始化(默认容量 16,负载因子 0.75)。 -
定位桶位置:
index = (n-1) & hash
。 -
处理哈希冲突:
-
链表插入:
-
JDK 1.7:头插法(易导致死循环)。
-
JDK 1.8:尾插法(解决死循环问题)。
-
-
树化处理:若链表长度 ≥8 且数组长度 ≥64,链表转红黑树。
-
-
覆盖或新增节点:
-
Key 已存在:覆盖 Value,返回旧值。
-
Key 不存在:插入新节点,返回 null。
-
-
扩容检查:若元素总数 > 阈值(容量 × 负载因子),触发
resize()
。
4. 扩容机制(resize())
触发条件:元素数量超过阈值(容量 × 负载因子,默认 0.75)。
扩容流程:
-
新容量计算:旧容量 × 2(保证容量始终为 2 的幂次)。
-
迁移元素:
-
JDK 1.7:遍历旧数组,重新哈希每个元素到新数组(头插法)。
-
JDK 1.8:优化迁移逻辑,链表元素拆分为高位链和低位链(无需重新哈希):
-
低位链:
原索引位置
。 -
高位链:
原索引位置 + 旧容量
。
-
-
优化原理:
由于新容量是旧容量的 2 倍,(newCap - 1) & hash
的结果仅取决于哈希值的第 log2(oldCap)
位是否为 1:
-
若为 0 → 索引不变(低位链)。
-
若为 1 → 索引 = 原索引 + 旧容量(高位链)。
5. 红黑树优化
树化条件:
-
链表长度 ≥
TREEIFY_THRESHOLD
(默认 8)。 -
数组长度 ≥
MIN_TREEIFY_CAPACITY
(默认 64)。
退化条件:
-
红黑树节点数 ≤
UNTREEIFY_THRESHOLD
(默认 6)。
优势:
-
链表查询复杂度 O(n),红黑树查询复杂度 O(logn),显著减少哈希冲突时的性能损耗。
6. 关键参数与默认值
参数 | 默认值 | 说明 |
---|---|---|
DEFAULT_INITIAL_CAPACITY | 16 | 默认初始容量 |
DEFAULT_LOAD_FACTOR | 0.75 | 负载因子(扩容阈值 = 容量 × 负载因子) |
TREEIFY_THRESHOLD | 8 | 链表转红黑树的阈值 |
UNTREEIFY_THRESHOLD | 6 | 红黑树退化为链表的阈值 |
MIN_TREEIFY_CAPACITY | 64 | 允许树化的最小数组长度 |
7. 性能优化建议
-
初始化容量:预估元素数量,避免频繁扩容(如预计存 1000 元素,初始容量设为 2048)。
-
重写 hashCode() 和 equals():确保 Key 对象的哈希分布均匀且相等性判断准确。
-
避免高频修改:多线程场景使用
ConcurrentHashMap
。
总结
-
核心结构:数组 + 链表/红黑树,动态扩容优化性能。
-
哈希设计:高位异或、位运算取模、红黑树优化冲突。
-
线程安全:非线程安全,需使用替代方案。
-
实战技巧:合理初始化容量、重写哈希方法、避免并发操作。