HashMap1.8源码解读之新增/修改——put()

本文详细分析了HashMap1.8中put()方法的实现过程,包括hash()函数如何降低哈希冲突,以及putVal()方法处理哈希冲突的细节,如链表转红黑树。同时介绍了put()方法如何处理已存在键的情况,以及在冲突和树化条件下的操作。
摘要由CSDN通过智能技术生成

一.引读

    这篇意在分析HashMap1.8源码的put()方法。对于基础数据结构的操作,无非就是增删改查,而这篇我们要阐述的就是JDK1.8里HashMap里的新增/修改,也就是put()方法。
    对于对HashMap还不熟悉的读者,建议先看下我之前写的拙作:
    1.HashMap源码解读之原理浅析—前传篇
    2.HashMap1.8-源码浅析1之成员变量及2个静态内部类阐述
    3.HashMap1.8-源码浅析2之初始化

    看下我们平常的使用:

1-1public void test(){
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("key", "value");
    }

注:在以下的展示的代码片段中,若是JDK的源码,我会copy出其方法的注释,并翻译出来,有些还会加上我自己的理解

二.HashMap put()中的hash()

上面 hashMap.put(“key”, “value”); 跟着往下,如下所示:

2-1/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     * 注释翻译:
     * 将指定值与此映射中的指定键相关联。如果映射以前包含键的映射,则旧的值被替换。
     * @param key 要与指定值关联的键
     * @param value 要与指定键关联的值
     * @return 键相关的上一个值,不存在(没有映射)或为空时返回null
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

接着看2-1中的hash()方法:

2-2:
    /**
     * Computes key.hashCode() and spreads (XORs) higher bits of hash
     * to lower.  Because the table uses power-of-two masking, sets of
     * hashes that vary only in bits above the current mask will
     * always collide. (Among known examples are sets of Float keys
     * holding consecutive whole numbers in small tables.)  So we
     * apply a transform that spreads the impact of higher bits
     * downward. There is a tradeoff between speed, utility, and
     * quality of bit-spreading. Because many common sets of hashes
     * are already reasonably distributed (so don't benefit from
     * spreading), and because we use trees to handle large sets of
     * collisions in bins, we just XOR some shifted bits in the
     * cheapest possible way to reduce systematic lossage, as well as
     * to incorporate impact of the highest bits that would otherwise
     * never be used in index calculations because of table bounds.
     * 注释翻译:
     * 计算key.hashCode()并传播(XORs)更高的散列位降低。因为该表使用2的幂掩蔽,所以仅在当前掩码
     * 上方以位变化的散列将总是发生碰撞。(已知的例子有一组浮点键在小表格中保存连续的整数。)
     * 所以我们应用扩展更高位影响的转换向下。在速度、效用和bit-spreading质量。
     * 因为有很多常见的哈希集合已经被合理分配了(所以不能从中受益)
     * 因为我们使用树来处理大的集合在箱子里的冲突,我们只是异或一些移位的位以最便宜的方式减少系统损失,
     * 以及以吸收最高位的影响,否则由于表边界的关系,永远不要在索引计算中使用。
     * 
     * 个人理解:简单来说,就是我们平常得到的HashCode()值用二进制表示(32位),有更大的可能大部分的数是在16位及以下,
     * 高位的16位的数比较少,所以用低16位和高16位这种进行异或(h = key.hashCode()) ^ (h >>> 16),加入高位的特征,
     * 使其更加的分散,从而减少hash的冲突
     * 简而言之,为了减少hash冲突
     */
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

这里还值得一说的就是,若key为null,那么其hash值一直都是0;

三.HashMap put()方法的实现 putVal()

3-1:
    /**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     * 
     * 注释翻译:
     * @param hash key的hash值
     * @param key key
     * @param value value
     * @param 若onlyIfAbsent = true, 那么不替换已经存在的value值
     * @param 若evict = false, 那么表示 表处于创建模式,这个字段用于扩展的这里没有什么用
     * @return 先前已有的映射的值,若没有或为null则是null
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //判断HashMap成员变量中的table是否存在
        if ((tab = table) == null || (n = tab.length) == 0)
        	//若HashMap成员变量中的table为空则new出table
            n = (tab = resize()).length;
        //对key的hash值对table的length取模,并以p记录下来取模后的头节点,判断头节点是否为空
        if ((p = tab[i = (n - 1) & hash]) == null)
        	//如果为空,说明还没有节点,那么把要放进来的key,value包装成一个头节点放入到里面
            tab[i] = newNode(hash, key, value, null);
        else {
        //若进入这里说明 有hash冲突
            Node<K,V> e; K k;
            //判断放进来的key是否与已存在的头节点的key相同
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                //若相同,则用e记录下来
                e = p;
            //判断已存在的头节点是树节点
            else if (p instanceof TreeNode)
            	//若是则以树节点放入的方式去处理,这里不详细说明,以后详细分析
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            	//若进入到这里说明,有hash冲突,并且和头节点的key不相同,然后遍历这条链
                for (int binCount = 0; ; ++binCount) {
                	//判断上一个遍历节点还有没有下一个节点
                    if ((e = p.next) == null) {
                    	//若没有,这说明已遍历完链上的节点,没有找到存在相同key的节点,那么把放进来的
                    	//key和value包装成一个节点对象,并放到链的最后面
                        p.next = newNode(hash, key, value, null);
                        //判断放完新的节点后,链上的节点树是否已达到TREEIFY_THRESHOLD(树化的阈值这里是8)
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        	//如果是,则考虑去树化,
                        	//这里面的逻辑并不是直接树化了,还看下此时hashMap的capacity(容量)是否已达到了64
                        	//若达到了64 才会去树化,否则是扩容
                            treeifyBin(tab, hash);
                        break;
                    }
                    //判断是否与遍历到的节点的key是否相同
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        //是相同则找到了已存在相同key的节点,跳出循环
                        break;
                    //把当前遍历到的节点e给p记录下来
                    p = e;
                }
            }
            //因为上面遍历了一轮之后e记录的就是 与放入进来的key相同的已存在节点
            if (e != null) { // existing mapping for key
            	//这里进来说明已找到存在相同key的节点,并且是e
                V oldValue = e.value;
                //看onlyIfAbsent值为false或者 存在相同key的节点对应的老value值为空,
                //那么用放入的新的value替换掉老value
                //注意这里若已存在相同key的节点,但oldValue == null则也会被替换掉
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                //返回存在相同key的节点对应的value值
                return oldValue;
            }
        }
        //变更次数+1
        ++modCount;
        //判断已放入的节点数是否超过了节点数阈值
        if (++size > threshold)
        	//若大于则扩容
            resize();
        //HashMap这里没啥用,用于扩展,比如LinkedHashMap
        afterNodeInsertion(evict);
        return null;
    }

总结:看我HashMap源码解读之原理浅析—前传篇就好,里面有思想的总结。
好了以上就是put()方法的整体流程了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值