HashMap 的 put 方法具体流程及关键步骤

HashMap 的 put 方法具体流程及关键步骤


前言

HashMap 的 put 方法是其核心操作之一,用于将键值对插入哈希表中。以下是其具体流程及关键步骤,结合不同版本的实现差异(如 JDK 1.7 与 1.8)进行说明:


一、核心流程

  1. 初始化检查
  • 首次插入时的延迟初始化:若 HashMap 的底层数组 table 未初始化(如调用无参构造方法后首次插入),则通过 resize() 方法创建默认容量为 16 的数组,并计算扩容阈值(容量 × 负载因子,默认 0.75)。
  • 扩容触发条件:当元素数量超过阈值时触发扩容(JDK 1.8 中严格按阈值判断,而 1.7 还需检查插入位置是否为空)。

2.计算哈希值与索引

  • 扰动函数:通过 hash(key) 方法对键的 hashCode() 进行二次哈希处理,减少低比特位冲突。具体实现为:
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

此操作将高 16 位与低 16 位异或,增强哈希分布的均匀性。

  • 索引计算:通过 (n - 1) & hash 确定键值对应数组的桶下标(n 为数组长度)。
  1. 处理哈希冲突
  • 空桶插入:若目标桶为空,直接创建新节点(Node 或 TreeNode)存入数组。
  • 非空桶处理:
    • 链表遍历:若桶内为链表,遍历检查是否存在相同键(通过 equals 方法):
      • 存在相同键:覆盖旧值并返回旧值。
      • 不存在相同键:将新节点插入链表尾部(JDK 1.8 改为尾插法,避免并发环状链表问题)。
    • 红黑树处理:若桶内为红黑树,调用 putTreeVal 方法插入或更新节点。
  1. 链表转红黑树
  • 转换条件:当链表长度 ≥ 8(TREEIFY_THRESHOLD)且数组容量 ≥ 64(MIN_TREEIFY_CAPACITY)时,链表转为红黑树以提高查询效率;若容量不足 64,优先扩容而非树化。
  1. 扩容机制
  • 触发时机:插入新节点后,若当前元素数量超过阈值,调用 resize() 进行扩容。
  • 扩容操作:
    • 容量翻倍:新数组长度为原数组的 2 倍(保持 2 的幂次特性)。

    • 数据迁移:遍历旧数组,根据新容量重新计算索引:

      • 链表拆分:JDK 1.8 优化迁移逻辑,通过 (e.hash & oldCap) == 0 判断节点是否留在原位置或迁移至 原索引 + 旧容量 的位置,减少哈希计算次数。

      • 红黑树拆分:若桶内为红黑树,拆分为链表或新的红黑树。

二、JDK 1.7 与 1.8 的差异

特性JDK 1.7JDK 1.8
数据结构数组 + 链表数组 + 链表/红黑树
插入方式头插法(可能引发死循环)尾插法(避免并发问题)
扩容条件需同时满足元素数 ≥ 阈值且插入位置非空元素数 ≥ 阈值即扩容
哈希计算优化通过高位异或减少冲突
迁移逻辑重新计算所有节点索引利用位运算优化索引计算

三、关键代码片段(JDK 1.8)

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

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;
    // 计算索引并处理空桶
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        // 检查首节点是否匹配
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // 处理红黑树
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // 遍历链表
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 链表转红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 更新值或插入新节点
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    // 检查扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

四、性能优化点

  1. 哈希扰动:通过高位异或减少哈希冲突概率。
  2. 红黑树优化:链表长度过长时转为红黑树,将查询复杂度从 O(n) 降至 O(logn)。
  3. 扩容策略:JDK 1.8 的迁移逻辑通过位运算优化,减少哈希值重新计算次数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值