HashMap源码之put()方法

Posted heaven-elegy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap源码之put()方法相关的知识,希望对你有一定的参考价值。

/**
 * 常用方法put
 */
public V put(K key, V value) {
    // 获取key的hash并命令putval方法替换现有值与非创建模式
    return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    // tab - 当前hash桶的引用
    // p - key所代表的节点(此节点不一定是目标节点,而仅仅是hash与桶长度的计算值相同而已)(它不为空时可能是链表或红黑树)
    // n - 当前桶的容量
    // i - key在桶中的下标(同p,不代表目标节点)
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 初始化局部变量tab并判断是否为空,初始化局部变量n并判断是否为0
    // PS: 源码中大量的使用了这种书写方法,不知道放在某写大厂里会怎么样(斜眼笑)
    if ((tab = table) == null || (n = tab.length) == 0)
        // 当tab为空或n为0时,表明hash桶尚未初始化,调用resize()方法,进行初始化并再次初始化局部变量tab与n
        n = (tab = resize()).length;
    
    // 初始化p与i
    // 这里使用了(n - 1) & hash的方式计算key在桶中的下标.这个在后面单独说明
    // 当p是否为空
    if ((p = tab[i = (n - 1) & hash]) == null)
        // p为空,调用newNode方法初始化节点并赋值到tab对应下标
        tab[i] = newNode(hash, key, value, null);
    else {
        // p不为空,发生碰撞.进行后续处理
        
        // e - 目标节点
        // k - 目标节点的key
        Node<K,V> e; K k;
        
        // 判断key是否相同.(这里除了比较key以外,还比较了hash)
        // 注意,这里同时初始化了局部变量k,但是在第二组条件不满足的情况下,没有使用价值,可以被忽略
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            // key相同,将e(目标节点)设置为p
            e = p;
        // 判断节点是否是红黑树
        else if (p instanceof TreeNode)
            // 确定时,直接委派处理
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 走到这里,代表当前节点为普通链表,进行遍历查找
            // 变量binCount只作为是否达到tree化的阈值判断条件.
            for (int binCount = 0; ; ++binCount) {
                
                // 获取链表的下一个元素,并赋值到e(此时e是一个中间变量,不确定是否是目标节点)
                // 第一次for循环时,p代表hash桶中的节点(同时也是链表的头部节点),之后一直等于p.next
                if ((e = p.next) == null) {
                    // 链表遍历到末尾
                    
                    // 向链表中追加新的节点
                    p.next = newNode(hash, key, value, null);
                    
                    // 判断当前链表长度是否达到tree阈值
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        // 调用treeifyBin方法直接处理
                        treeifyBin(tab, hash);
                    // 中断循环
                    // 注意,此时局部变量e=null
                    break;
                }
                
                // 能走到此处,说明链表未结束,比较e的k是否相同(hash与==)
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // key相同
                    break;
                
                // e既不为null也不是目标节点,赋值到p,住被进行下次循环
                p = e;
            }
        }
        
        // 判断e是否存在
        if (e != null) { // existing mapping for key
            // e不等于null说明操作为"替换"
            
            // 缓存老值
            V oldValue = e.value;
            // 判断是否必须替换或老值为null
            if (!onlyIfAbsent || oldValue == null)
                // 必须替换或老值为空,更新节点e的value
                e.value = value;
            // 调用回调
            afterNodeAccess(e);
            // 返回老值
            // 注意,这里直接返回了,而没有进行modCount更新与下面的后续操作
            return oldValue;
        }
    }
    
    // 除了更新链表节点以外,都会走到这里(putTreeVal的返回值是什么有待确认)
    // modCount+1
    ++modCount;
    // size+1(元素数量+1)
    // 判断是否超过阈值
    if (++size > threshold)
        // 重置大小
        resize();
    // 调用后置节点插入回调
    afterNodeInsertion(evict);
    return null;
}

以上是关于HashMap源码之put()方法的主要内容,如果未能解决你的问题,请参考以下文章

源码之HashMap

JDK源码--HashMap(之resize)

HashMap源码解析之resize方法

源码之ConcurrentHashMap

源码之ConcurrentHashMap

源码分析——HashMap的put,resize,containskey方法原理分析