当然可以!我们来深入分析一下 Java 中 HashMap
的内部实现机制(以 JDK 8 为主),包括数据结构、核心算法、源码设计、以及适用场景。
HashMap
是基于哈希表实现的 Map
,底层结构是数组 + 链表 + 红黑树的结合体:
transient Node<K,V>[] table; // 主体数组,哈希桶(bucket)
主要流程:
计算 key 的 hash 值并映射到桶
int hash = hash(key);
int index = (n - 1) & hash; // 取模操作(位运算优化)
判断是否存在冲突
链表长度超过阈值(8):
是否触发扩容?
resize()
。HashMap
时合理指定初始容量。int newCap = oldCap << 1;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; // 链表结构
}
若该桶变为红黑树,则会变成 TreeNode(继承 Node):
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
boolean red;
}
为了避免哈希冲突,JDK 8 对 hashCode()
结果做了“扰动处理”:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这种处理方式能让高位参与低位运算,提高 hash 值的随机性,从而减少哈希冲突。
条件 | 是否转为红黑树 |
---|---|
链表长度 > 8,且数组长度 >= 64 | ✅ 转为红黑树 |
链表长度 > 8,数组长度 < 64 | ❌ 不转,优先扩容 |
树节点个数 < 6 | ✅ 转回链表(treeify → untreeify) |
这样做的好处是避免在小容量下使用红黑树带来的额外复杂度和性能负担。
操作 | 最佳情况 | 最坏情况(大量哈希冲突) |
---|---|---|
put/get/remove | O(1) | O(log n)(红黑树)或 O(n)(链表) |
resize | O(n) | O(n) |
Collections.synchronizedMap(new HashMap())
ConcurrentHashMap
(更高效,分段锁/红黑树优化)适合以下场景:
hashCode()
方法);从 HashMap.java
的以下方法入手:
putVal(...)
:添加元素的核心逻辑resize()
:扩容处理treeifyBin(...)
:链表转红黑树getNode(...)
:查找逻辑