基本结构
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认容量 16
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认负载因子
static final int TREEIFY_THRESHOLD = 8; // 转红黑树的阈值
static final int UNTREEIFY_THRESHOLD = 6; // 红黑树转链表的阈值
static final int MIN_TREEIFY_CAPACITY = 64; // 树化的最小容量
transient Node<K,V>[] table; // 哈希桶数组
transient Set<Map.Entry<K,V>> entrySet;
transient int size; // 当前元素个数
int threshold; // 扩容阈值
final float loadFactor; // 实际负载因子
}
- 若链表长度超过 TREEIFY_THRESHOLD 且 table 容量 ≥ MIN_TREEIFY_CAPACITY,会转为红黑树结构。
数组 + 链表 / 红黑树:
HashMap内部维护了一个Node<K, V>[] table数组。这个数组是哈希表的“桶数组 (Bucket Array)”。- 每个数组元素称为一个 桶 (Bucket)。
- 每个桶在正常情况下存储一个链表的头节点(
Node对象)。 - 关键改进 (JDK 8+): 当一个桶中的链表长度超过阈值 (默认为 8),并且当前
HashMap的总容量大于等于 64 时,该桶中的链表会转换为红黑树 (TreeNode)。当桶中的节点数减少到 6 时,树会转换回链表。
Node 节点结构
static class Node<K, V> implements Map.Entry<K, V> {
final int hash; // 存储键的哈希码 (经过扰动计算后的)
final K key;
V value;
Node<K, V> next; // 指向链表中的下一个节点 (如果是树节点,则是 TreeNode 的子类)
// ... 构造方法、getter/setter 等
}
- 每个桶是一个链表或红黑树,Node 是其基本单元。
为什么不在最开始就使用红黑树
- 空间开销较大,红黑树不划算
- 链表操作更简单、更快(在小数据量时)
- 绝大多数哈希桶的链表都很短
put操作
-
刚开始时,容量为0,在第一次put时进行resize。
-
计算哈希码:根据
key的hashCode()方法得到原始哈希码h。 -
扰动函数:使高位和低位的都参与进来,防止只取模只有低位运算
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } -
计算桶索引:
index = (length - 1) & hash,这里&(按位与) 操作比%(取模) 更快,要求桶数组长度必须是 2 的幂次方才能保证& (length-1)等价于% length且分布均匀。 -
处理碰撞
-
检查扩容
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;
// 计算桶下标 i = (n - 1) & hash
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 第一个节点就匹配
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 红黑树节点处理
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);
// 尝试链表转红黑树
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, i);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // 替换旧值
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
return null;
}
resize
-
第一次
put初始化时。 -
当
size > threshold,即元素个数超过负载因子 × 当前容量。 -
当链表长度大于8,尝试转为红黑树,但是数组长度 < 64时,尝试优先扩容。
-
新容量与阈值一般翻倍;迁移时不重新计算 hash
-
老容量 oldCap 必为 2 的幂;索引规则:idx = (n - 1) & hash。
-
扩容后 newCap = oldCap << 1,新掩码 newMask = (newCap - 1) = (oldMask << 1) | 1。
-
若 (hash & oldCap) == 0 → 新桶仍为 idx(低位链 lo)
-
若 (hash & oldCap) != 0 → 新桶变为 idx + oldCap(高位链 hi)
-
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; // 直接返回旧表
}
// 否则将容量扩大一倍(左移1位)
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // 阈值也翻倍
}
// 如果在初始化中传入了容量,此前会存入到oldThr当中,此时赋值给newCap
else if (oldThr > 0)
newCap = oldThr;
// 初始化阶段,使用默认初始容量和默认负载因子计算阈值
else {
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 初始化设置了容量,此时去设置新的扩容阈值
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; // 释放旧引用以便GC回收
if (e.next == null)
// 如果该桶只有一个节点,直接放到新表中对应位置
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
// 如果是红黑树节点,调用TreeNode的split方法重新分配
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else {
// 是链表结构,需要重新分配到新表
Node<K,V> loHead = null, loTail = null; // 原位置链表
Node<K,V> hiHead = null, hiTail = null; // 原位置+oldCap的链表
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 {
// 放到原位置 + oldCap 的新位置
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; // 返回新的哈希表
}
get
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;
// 如果列表不为空,且长度大于0,且当前桶不为空
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 先检查桶中的第一个节点是否匹配(hash和key都匹配)
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 如果第一个节点后面还有节点
if ((e = first.next) != null) {
// 红黑树
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
// 否则遍历链表节点,查找匹配节点
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}