天天看点

Java8 HashMap put方法源码解析

Java8 put源码解析,大致可分为如下步骤:

一:判断是不是第一次插入,如果是,则进行resize()

二:如果通过hash&n-1计算出来的下标,里面如果没有元素,则在改下标下创建一个新的元素,然后put操作完成,然后判断是否扩容

三:如果计算的下标下面已经存在元素,通过key值进行判断是否相同,如果相同,则结束,判断操作,在后去代码中,将新的值,覆盖为老的值,并返回老的值

四:如果下标存在的值与要插入的元素的key值不相同,则进入循环,因为在hashmap中,一个下标下的元素可能有多个值,原因是不同的key值在进行hash后的值可能相同,所以在同一个下标下,可能有多个key值的hash相同,key不相同的元素.所以要进行循环,直到查到对应的元素,或者查询到节点下最后一个元素,才会跳出循环,在一个下标下,如果元素的个数小于8则是以链表的形式进行存储的,如果大于8则是以红黑树进行存储的

五:如果插入的是新元素,则判断插入后的容量是否,已经超过了当前容量,如果超过了,则进行扩容

源码如下:

put方法:

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

putVal方法:

/**
     * Implements Map.put and related methods
     *
     * @param hash hash for keyput
     * @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
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        /**
         * 判断tab是不是为空,如果为空,则将容量进行初始化,也就是说,初始换操作不是在new HashMap()的时候进行的,而是在第一次put的时候进行的
         * 这一块是有雄线程问题的.
         */
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

        /**
         * 初始化操作以后,根据当前key计算当前值的下标,如果下标对应的位置,没有元素,则将值放到这个位置下,并将,当前节点的元素赋值给p
         */
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

        /**
         * 如果对应的位置已经有元素的情况,走到这一步,说明元素 P 不为空,且不等于null
         */
        else {
            Node<K,V> e; K k;

            //判断这个这个元素的key是否与要放进来的的key是否相同,如果相同,则把这个当前这个位置的元素赋值给e,用于后期判断
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //如果key不相同,则判断当前节点是不是TreeNode(红黑树),如果是,则进行红黑树的插入
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

            //如果不是红黑树,怎进行链表操作
            else {

                //binCount 是为了计算,当前hash的位置下,元素的个数,用于判断binCount >= TREEIFY_THRESHOLD - 1,如果成立,则转换成红黑树
                for (int binCount = 0; ; ++binCount) {

                    /**
                        代码运行 if ((e = p.next) == null) 里面的语句,说明,已经循环到的最后一个元素
                        判断当前元素的下一个节点是不是为空,其实就是为了判断,当前元素是不是,该下标下的最后一个元素的语句
                        如果是最走一个元素,则将要插入的新元素,放到当前元素的后面
                        然后判断,当前下标下的元素个数是是否>=8个,如果大于则将当前下标下的所有元素,从链表改成红黑树,并跳出循环
                     */
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //判断元素个数是不是>=8
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //转换成红黑树
                            treeifyBin(tab, hash);
                        break;
                    }

                    /**
                     * 如果不是当前下标下的最后一个元素,并且找到了,与插入的数据的key相同的元素,则跳出循环,e 的赋值在上一次的循环中已经赋值过了
                     */
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    /**
                        把当前下标下,找到的节点,赋值给p并进行下一次循环
                        走到这一步,说明,在循环当前下标下元素的key时,还没有到结尾,并且也没有找到,与要插入的元素的key相同的元素,所以,继续循环
                     */
                    p = e;
                }
            }

            /**
                e != null 说明,根据key的hash值算出来的下标下有元素,原因是,在上面的循环过程中,如果有元素,e都不为空
                只要e不为空,说明e的key与要插入的元素的key是相同的,判断是否覆盖onlyIfAbsent,然后返回原来oldValue
                如果走下面这些代码块,则说明,没有新的元素插入,不需要进行容量判断
             */
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        /**
         * 如果走到下面这些代码,这说明有新的元素插入,因为如果key值已经存在,在上面的代码中已经返回了,根本不会走到下面.
         * 判断容量是否已经到了需要扩充的阈值了,如果到了,则进行扩充
         */
        if (++size > threshold)
            resize();

        afterNodeInsertion(evict);
        return null;
    }