面试必考!HashMap的put方法源码逐行解析,看完再也不怕被问
🔥 先看全貌:put方法整体流程

第1步:hash扰动——为什么不是直接用hashCode?
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
💡 面试加分话术:”JDK1.8的扰动只做了一次异或,JDK1.7做了4次(9次扰动)。1.8认为一次就够了,省CPU嘛。”
第2步:putVal——核心方法,100行源码的精华
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}
第3步:定位桶——第一个关键判断
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;// ② 定位桶:用(n-1)&hash,等价于hash%n,但位运算更快if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null); // 桶为空,直接插入
💡 面试追问:”HashMap容量为什么是2的幂?”——答:为了用位运算替代取模,提升性能。tableSizeFor方法保证容量是2的幂。
第4步:处理hash冲突——三种情况
else {Node<K,V> e; K k;// 情况1:桶里第一个节点的key就和我要放的一样if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;// 情况2:桶里已经变成红黑树了else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);// 情况3:还是链表,遍历找位置else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null); // 尾插法!// 链表长度达到8,转红黑树if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}
| 情况 | 条件 | 处理方式 |
|---|---|---|
| key相等 | hash相同 && (==或equals) | 覆盖旧值 |
| 红黑树 | 节点是TreeNode | 调用putTreeVal |
| 链表 | 其他情况 | 尾插法遍历插入 |
💡 红黑树节点是链表节点的2倍大,所以转树是”最后手段”,不是”越早越好”。而且还有个下限:红黑树节点<=6时会退化回链表(UNTREEIFY_THRESHOLD=6)。
第5步:覆盖旧值 & 扩容判断
// 如果找到了已存在的key,覆盖旧值if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e); // 回调,LinkedHashMap用的return oldValue; // 返回旧值!put的返回值就是这么来的}}++modCount;// 扩容判断:元素数量 > 扩容阈值if (++size > threshold)resize();afterNodeInsertion(evict); // 回调,LinkedHashMap用的return null; // 新插入返回null
💡 面试追问:”为什么负载因子是0.75?”太小(0.5)→ 浪费空间;太大(1.0)→ 冲突太多。0.75是时间和空间的折中。源码注释还说了:0.75让threshold恰好是整数(16*0.75=12)。
📊 全流程总结表

🧠 小测验:你真的懂了吗
💬 评论区顺便说说你选了哪个,以及为什么——说出理由才算真懂了!
🎁 彩蛋答案(先思考,再看答案)
这些是面试追问的完整答案,先自己想想再看哦 👇
📌 版权声明:原创不易,转载请注明出处。觉得有用就点个在看👍,你的支持是我持续输出的动力!
夜雨聆风