HashMap的get、put流程源码分析

get 方法源码如下

java 复制代码
final Node<K, V> getNode(int hash, Object key) {
        Node<K, V>[] tab;    // 哈希桶数组引用
        Node<K, V> first, e; // first: 桶内首节点; e: 遍历用临时节点
        int n; K k;          // n: 数组长度; k: 临时存储节点的键

        // 1. 检查哈希表状态:数组非空且长度>0,且目标桶存在节点
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            
            // 2. 优先检查首节点:哈希值匹配 + 键匹配(引用或 equals)
            if (first.hash == hash && 
                ((k = first.key) == key || (key != null && key.equals(k)))) {
                return first;
            }

            // 3. 存在后续节点时,区分链表/红黑树处理
            if ((e = first.next) != null) {
                // 3.1 红黑树节点:调用树查找方法
                if (first instanceof TreeNode) {
                    return ((TreeNode<K, V>) first).getTreeNode(hash, key);
                }
                // 3.2 链表节点:遍历查找
                do {
                    if (e.hash == hash && 
                        ((k = e.key) == key || (key != null && key.equals(k)))) {
                        return e;
                    }
                } while ((e = e.next) != null); // 遍历到链表末尾
            }
        }
        // 4. 未找到节点
        return null;
    }

get方法通过getNode实现键值对查找,核心是利用哈希定位与数据结构适配实现高效查询,步骤如下:

1.哈希值计算

调用hash(key)生成二次哈希值,确保与插入时的定位逻辑一致。

2.索引定位与节点查找

计算索引后,检查哈希表状态及目标位置:

哈希表为空、长度为 0 或索引位置无节点:返回null;

索引位置首节点键匹配:直接返回该节点;

首节点不匹配时,根据结构处理:

红黑树节点:调用getTreeNode从根节点开始按红黑树规则查找;

链表节点:遍历链表逐一比对,找到匹配节点则返回,否则返回null。

3.效率优化

优先判断首节点(最常访问),避免无效遍历;红黑树将查找复杂度从链表的 O (n) 降至 O (log n),平衡查询效率。

put 方法源码如下

java 复制代码
// 核心插入方法
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        Node<K, V>[] tab; 
        Node<K, V> p; 
        int n, i;

        // 1. 哈希表未初始化或为空时,进行扩容(初始化)
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

        // 2. 计算索引,定位哈希桶,桶为空则直接插入新节点
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K, V> e; 
            K k;

            // 3. 检查首节点是否匹配(哈希和键都匹配)
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            // 4. 首节点是红黑树节点,调用红黑树插入逻辑
            else if (p instanceof TreeNode)
                e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
            // 5. 首节点是链表节点,遍历链表处理
            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;
                }
            }

            // 6. 处理已存在的节点,更新值
            if (e != null) { 
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e); 
                return oldValue;
            }
        }

        // 7. 插入新节点后,检查是否需要扩容、更新计数等
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict); 
        return null;
    }

put方法的核心是通过putVal实现键值对的插入或更新,整体流程围绕哈希计算、节点定位、结构适配及动态扩容展开,具体步骤如下:

1.哈希值计算

调用hash(key)对键进行二次哈希(高 16 位与低 16 位异或),减少哈希冲突,为后续定位存储位置提供依据。

2.哈希表初始化 / 扩容

若哈希表(table)未初始化(为空或长度为 0),通过resize()方法完成初始化(默认容量 16,负载因子 0.75,阈值 12);若后续元素数量超过阈值,resize()会将容量翻倍(确保为 2 的幂次方),维持高效存储。

3.索引定位与节点插入

利用(n-1) & hash计算存储索引(n为当前容量,位运算等价于取模且更高效),根据索引位置状态处理:

位置为空:直接创建新节点插入;

位置非空:

首节点键匹配(哈希值与键均相等):更新节点值;

首节点为红黑树节点:调用putTreeVal按红黑树规则插入;

首节点为链表节点:遍历链表,找到匹配节点则更新值,否则尾部插入新节点;当链表长度达到树化阈值(8)且容量≥64 时,调用treeifyBin转为红黑树(容量不足 64 时优先扩容)。

4.计数与扩容检查

插入新节点后,modCount(结构修改计数器)自增,size(元素总数)加 1;若size超过阈值,触发resize()扩容,保证哈希表性能。

相关推荐
地平线开发者6 分钟前
征程 6M 部署 Omnidet 感知模型
算法·自动驾驶
秋说29 分钟前
【PTA数据结构 | C语言版】线性表循环右移
c语言·数据结构·算法
浩瀚星辰20241 小时前
图论基础算法:DFS、BFS、并查集与拓扑排序的Java实现
java·算法·深度优先·图论
oioihoii3 小时前
C++随机打乱函数:简化源码与原理深度剖析
开发语言·c++·算法
不知名。。。。。。。。4 小时前
分治算法---快排
算法
minji...4 小时前
数据结构 算法复杂度(1)
c语言·开发语言·数据结构·算法
凌肖战4 小时前
力扣网编程150题:加油站(贪心解法)
算法·leetcode·职场和发展
吃着火锅x唱着歌4 小时前
LeetCode 3306.元音辅音字符串计数2
算法·leetcode·c#
不見星空4 小时前
【leetcode】1751. 最多可以参加的会议数目 II
算法·leetcode
不見星空4 小时前
leetcode 每日一题 3439. 重新安排会议得到最多空余时间 I
算法·leetcode