第四步是 “插入或更新元素”—— 这是实际执行数据存储的步骤,根据第三步(处理哈希冲突)的判断结果,要么更新已有 Key 的 value,要么将新 Key-value 插入到对应的数据结构中。
第四步的核心操作:两种场景的处理
经过第三步的冲突处理后,会出现两种明确的结果:“找到相同的 Key” 或 “确认是新 Key”,第四步针对这两种场景分别操作。
场景 1:找到相同的 Key(需要更新 value)
如果在第三步的遍历中(无论是链表还是红黑树),发现了与新 Key “相同” 的节点(哈希值相同且equals()
返回 true),则执行更新操作:
- 用新的 value 覆盖该节点的旧 value;
- 返回被覆盖的旧 value(这也是
put
方法的返回值逻辑:如果 Key 已存在,返回旧值;否则返回 null)。
举例:
HashMap 中已有 ("name", "张三")
,现在调用put("name", "李四")
:
- 第三步会找到 “name” 对应的节点;
- 第四步将该节点的 value 从 “张三” 更新为 “李四”;
put
方法返回 “张三”。
场景 2:确认是新 Key(需要插入新节点)
如果第三步遍历完所有节点后,未发现相同的 Key,则确认是新 Key,执行插入操作:
- 创建新节点:根据当前桶的结构,创建对应的节点(链表节点
Node
或红黑树节点TreeNode
)。 - 插入节点:
- 若桶是链表:将新节点插入链表尾部(尾插法,JDK 1.8 特性);
- 若桶是红黑树:按照红黑树的插入规则将新节点加入树中,并维持树的平衡。
- 更新元素计数:HashMap 的元素数量
size
加 1(size++
),表示新增了一个键值对。
举例:
HashMap 中已有 ("name", "张三")
,现在调用put("age", 20)
:
- 假设 “age” 计算出的索引与 “name” 不同(无冲突),直接在对应桶插入新节点;
- 若有冲突(索引相同),则在链表尾部或红黑树中插入 “age” 节点;
size
从 1 变为 2,put
方法返回 null。
第四步的核心目标
- 保证数据准确性:对于重复 Key,通过更新 value 维持 “Key 唯一” 的特性;对于新 Key,正确存储到对应的数据结构中。
- 维护内部状态:通过更新
size
计数,为后续的扩容判断(第五步)提供依据。
与其他步骤的关联
- 依赖第三步的结果:只有明确是 “更新” 还是 “插入”,第四步才能执行具体操作;
- 为第五步铺垫:插入新节点后
size
增加,才需要判断是否超过阈值(threshold
),进而触发扩容。
归纳
第四步是 HashMap 完成数据存储的 “落地” 步骤:通过 “更新” 或 “插入” 两种操作,将键值对最终存入容器中,同时维护 HashMap 的核心状态(size
和数据结构完整性)。这一步完成后,就会进入最后一步 —— 判断是否需要扩容。