文章目录
一、你以为的HashMap可能都是错的!
(先来个灵魂拷问)当我们写下new HashMap<>()时,这个看起来平平无奇的集合容器,真的是你以为的"数组+链表"这么简单吗?实际上,JDK8之后的HashMap暗藏玄机!今天我们就来扒开它的底裤(划重点),看看这个面试常客到底藏着哪些不为人知的秘密!
二、HashMap的存储结构大揭秘
2.1 核心成员三剑客
transient Node<K,V>[] table; // 哈希桶数组(核心中的核心!)
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 负载因子(黄金比例!)
int threshold; // 扩容阈值(生死线!)
想象一个图书馆的书架(table数组),每个书架格子(bucket)可以放多本书(Entry节点)。当某个书架堆满时,管理员会把书搬到大书架(扩容)!
2.2 链表转红黑树的临界点
(超级重要!!!)当链表长度≥8且数组长度≥64时,链表会变身红黑树!这个设计简直妙啊——既保证了查询效率(O(n)→O(logn)),又避免过早树化浪费资源。
![JDK8 HashMap结构示意图(此处应有伪代码描述)]
数组索引0 → null
数组索引1 → Node1 → Node2 → TreeNode3(红黑树结构)
数组索引2 → NodeA → NodeB
三、Put方法执行全流程(带完整断点分析)
3.1 完整执行链路
putVal(hash(key), key, value)- 判断table是否为空 → 初始化扩容
- 计算数组下标:
(n-1) & hash - 三种情况处理:
- 坑位为空 → 直接插入
- 坑位是红黑树 → 树节点插入
- 坑位是链表 → 遍历插入(尾插法!JDK8改进点)
- 判断是否需要树化
- 判断是否需要扩容
3.2 哈希计算的黑科技
来看这个魔法代码:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
(重点理解!)这个异或操作让高位参与哈希运算,有效减少哈希碰撞。就像调鸡尾酒——把高位风味和低位基酒充分混合!
四、扩容机制:最烧脑的精华部分
4.1 扩容触发条件
当size > threshold(容量*负载因子)时触发,比如默认初始容量16,当放入第13个元素时(16*0.75=12),就会触发第一次扩容。
4.2 重新哈希的优化
JDK8的扩容后元素迁移太秀了!通过(e.hash & oldCap) == 0判断元素位置:
- 等于0:留在原索引
- 不等于0:新位置=原索引+旧容量
这种方法避免了重新计算哈希值,直接通过位运算确定新位置!
五、高频面试考点合集(背完薪资+2k)
5.1 必考八股文
- HashMap线程不安全的表现(死循环、数据丢失)
- 为什么用红黑树不用AVL树?(红黑树插入更快)
- 初始容量设为1000实际是多少?(1024,2的幂)
- Key为null存放在哪?(table[0]的位置)
- 为什么重写equals必须重写hashCode?(保证相同对象哈希值一致)
5.2 实战踩坑记录
(血泪教训!)曾经在并发场景下使用HashMap导致CPU100%,最后发现是哈希碰撞导致链表无限增长。改用ConcurrentHashMap后问题解决!
六、灵魂拷问环节
(面试官压轴问题)既然红黑树这么好,为什么不直接全部用红黑树?因为树节点占用空间是普通节点的两倍,内存和性能需要平衡!这体现了JDK设计者精妙的空间换时间哲学。
下次面试被问HashMap时,你可以微微一笑:“您想听JDK7版本的死循环问题,还是JDK8的红黑树优化?” 掌握本文内容,offer还不是手到擒来!(记得点赞收藏防丢失)
&spm=1001.2101.3001.5002&articleId=148089359&d=1&t=3&u=14c2ebda752d41479b216d779e724e52)
1076

被折叠的 条评论
为什么被折叠?



