HashMap的put()方法执行流程是:先处理null键(存入索引0),再对非null键计算扰动哈希值(h ^ h>>>16),通过hash & (table.length-1)定位桶;桶空则直接插入,否则遍历链表或红黑树比较key,存在则覆盖value,不存在则尾插或树化;最后检查size > threshold触发resize扩容。

Java 中 HashMap 存储和获取键值对,核心靠三步:算哈希、定位置、查链/树。它不遍历整个集合,而是用哈希函数把键快速映射到数组下标,实现接近 O(1) 的效率。
存储数据:put() 是怎么放进去的?
调用 put(K key, V value) 时,实际执行以下逻辑:
- 先判断 key 是否为 null —— null 键被固定存入数组索引 0 的位置(特殊处理)
- 非 null 键则调用 key.hashCode(),再经扰动函数 (h ^ h>>>16) 降低低位哈希冲突概率
- 用 hash & (table.length - 1) 快速计算数组下标(要求 table 长度是 2 的幂)
- 定位到桶(bucket)后:
- 桶为空 → 直接新建 Node 放入
- 桶有元素 → 先用 == 或 equals() 比较 key 是否已存在;存在则覆盖 value
- 不存在则链表尾插(JDK 1.8+),或插入红黑树(当链表长度 ≥ 8 且数组长度 ≥ 64)
- 插入后检查是否需扩容:当 size > threshold(容量 × 加载因子,默认 0.75) 时触发 resize(),数组长度翻倍并重哈希所有元素
获取数据:get() 是怎么找出来的?
调用 get(Object key) 时,并不遍历 map,而是精准跳转:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- null 键直接查 table[0]
- 非 null 键再次计算扰动哈希值,再用 hash & (table.length - 1) 定位桶
- 拿到桶首节点后:
- 如果是普通 Node,从头开始逐个比较 key(== 或 equals)
- 如果是 TreeNode(红黑树),走左/右子树比较逻辑,时间复杂度 O(log n)
- 匹配成功即返回 value;失败返回 null(注意:value 本身也可能为 null,不能据此判断 key 是否存在)
几个关键细节不能忽略
HashMap 正确工作的前提是键对象满足两个契约:
立即学习“Java免费学习笔记(深入)”;
- hashCode() 和 equals() 必须一致:相等的 key(equals 返回 true)必须返回相同的 hashCode,否则同一逻辑 key 可能被散列到不同桶,导致 get 失败
- 作为 key 的对象应尽量不可变:如果 key 在放入 map 后修改了影响 hashCode/equals 的字段,后续 get 就可能找不到它
- null 键只允许一个,但 null 值可以多个;重复 put 相同 key 会覆盖旧值(如需追加,可将 value 设为 List 并配合 computeIfAbsent)
常用辅助方法帮你少踩坑
除了基础的 put/get,这些方法在实战中更安全高效:
- putIfAbsent(key, value):仅当 key 不存在时才插入,避免误覆盖
- getOrDefault(key, defaultValue):取不到时不返回 null,而是给默认值(统计词频常用)
- computeIfAbsent(key, mappingFunction):key 不存在则创建新值(比如 new ArrayList()),存在则直接操作原值,适合多值映射
- remove(key, value):只有键值都匹配才删除,比单纯 remove(key) 更精确

















