📚 核心目录
设计哲学与版本演进
JDK1.2
数组+链表
JDK1.5引入ConcurrentHashMap
JDK1.8树化优化
红黑树引入
尾插法替代头插法
hash算法优化
基础数据结构剖析
存储单元结构(JDK1.8+)
static class Node<K,V> implements Map.Entry<K,V> { final int hash; // 扰动后的哈希值 final K key; V value; Node<K,V> next; // 链表指针 // 树节点扩展 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; } }
哈希函数实现奥秘
JDK1.8扰动算法
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
索引计算原理
// (n-1) & hash 代替模运算(要求n必须是2的幂) index = (table.length - 1) & hash;
核心方法源码解读
putVal方法流程图
是
否
是
否
是
否
是
开始
计算key哈希
桶是否空?
新建节点存入
是树节点?
红黑树插入
遍历链表
找到相同key?
覆盖value
尾部插入新节点
长度>=8?
转换为红黑树
树化阈值控制逻辑
static final int TREEIFY_THRESHOLD = 8; // 链表转树阈值 static final int UNTREEIFY_THRESHOLD = 6; // 树转链表阈值 static final int MIN_TREEIFY_CAPACITY = 64; // 最小树化容量
扩容机制深度分析
resize() 核心步骤
-
计算新容量(oldCap << 1)
-
创建新数组
-
数据迁移:
-
单节点:直接rehash
-
链表:拆分为高低位链(基于高位bit)
-
红黑树:拆分为红黑树结构或退化为链表
-
高低位拆分原理
// JDK1.8扩容优化关键代码 Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; next = e.next; if ((e.hash & oldCap) == 0) { // 判断高位bit if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; }
线程安全问题全解
经典死链问题(JDK1.7)
线程AEntry链线程B注意系统执行头插法同时修改next指针产生环形链表导致死循环线程AEntry链线程B注意系统
并发优化方案对比
线程安全实现方案
方案 | 原理 | 适用场景 |
---|---|---|
Collections.synchronizedMap | 方法级synchronized锁 | 低并发场景 |
ConcurrentHashMap | 分段锁+CAS | 高并发读写 |
ReadWriteLock | 读写分离锁 | 读多写少场景 |
Hashtable | 全表锁 | 已淘汰,不推荐使用 |
性能调优实践指南
初始化参数最佳实践
// 预期存储100个元素,负载因子0.75 int expectedSize = 100; float loadFactor = 0.75f; int initialCapacity = (int) Math.ceil(expectedSize / loadFactor); Map<String, Object> optimizedMap = new HashMap<>(initialCapacity, loadFactor);
高频面试题破解
Q1:为什么树化阈值是8?
-
泊松分布统计:哈希冲突达到8的概率不足千万分之一
-
平衡链表与树的性能:树查找O(logn) vs 链表O(n)
Q2:HashMap为什么使用红黑树?
-
相较于AVL树,红黑树牺牲少许平衡性换取更快的插入/删除速度
-
在哈希冲突严重的场景下保证基础查询性能
总结与思考
-
数据结构选择:为什么HashMap不直接使用红黑树存储?
-
版本演进:为什么JDK1.8将头插法改为尾插法?
-
并发安全:如何设计高并发场景下的安全哈希表?