记录下HashMap下的putVal()和resize()方法的底层源码中文注释

 /**
     * 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
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 判断table数组是否为空或者长度为0
        if ((tab = table) == null || (n = tab.length) == 0)
            // 是则进行resize扩容
            n = (tab = resize()).length;
        // 根据hash & (length - 1)获取到数组下标,判断当前数组下标是否存在
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 不存在则直接创建一个新的node节点插入
            tab[i] = newNode(hash, key, value, null);
        //tab[i] 存在则进行一下处理
        else {
            Node<K,V> e; K k;
            // 判断节点中的hash是否等于插入的hash值以及key == key,存在则直接覆盖,然后直接返回
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                // 将p赋值给e,在下方进行了新值替换旧值,并直接返回旧值
                e = p;
            // 如果p,也就是table[i]是一个红黑树,则进行红黑树逻辑的插入
            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);
                        // 插入完成后,判断节点数量是否达到红黑树要求的阈值(8),是就转化为红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        // 插入最末的节点后跳出循环
                        break;
                    }
                    // 判断链表中节点的key值与插入的元素的key值是否相等
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        // 相等则调户循环
                        break;
                    // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
                    p = e;
                }
            }
            // 表示在桶中找到key值,hash值与插入元素相等的结点
            if (e != null) { // existing mapping for key
                // 记录旧值
                V oldValue = e.value;
                // onlyIfAbsent一直为true, 因此这里是直接覆盖
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                // 访问后回调?
                afterNodeAccess(e);
                // 返回旧的value
                return oldValue;
            }
        }
        // 结构性修改
        ++modCount;
        // 如果当前实际大小(size + 1) > 阈值则进行扩容
        if (++size > threshold)
            resize();
        // 插入后回调
        afterNodeInsertion(evict);
        return null;
    }

    /**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     * 扩容方法 实际开发中尽量避免resize()
     *
     * @return the table
     */
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            // 如果超过了最大容量,则不再进行扩容,就随他进行碰撞
            if (oldCap >= MAXIMUM_CAPACITY) {
                // 将阈值设置为最大整型值 0x7fffffff 2^31 - 1
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 如果扩容为原来的2倍都没超过最大容量则扩容2倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
                // 设置新的阈值为旧的阈值的2倍
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        // 如果构造器为无参构造器时执行此路基
        else {               // zero initial threshold signifies using defaults
            // 设置容量为16
            newCap = DEFAULT_INITIAL_CAPACITY;
            // 设置阈值为0.75 * 16 = 12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // 计算新的resize上限,这里是获取阈值的大小
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                    (int)ft : Integer.MAX_VALUE);
        }
        // 将阈值赋值给threshold
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
                // 初始化数组,并设置容量
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        // 如果老的数组存在,则需要进行数组元素迁移
        if (oldTab != null) {
            // 把每个bucket都移动到新的buckets中
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                // 如果当前数组下标索引元素不为空
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    // 当前桶不存在多个元素时
                    if (e.next == null)
                        // 将当前元素rehash后插入到新数组中
                        newTab[e.hash & (newCap - 1)] = e;
                    // 当前桶如果为红黑树结构
                    else if (e instanceof TreeNode)
                        // 进行红黑树相关的操作迁移
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    // 如果为链表
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        // 遍历链表
                        do {
                            next = e.next;
                            // 如果节点hash & 旧容量 == 0代表当前的hash后的位置依然在原来的位置
                            // 这里可能比较难理解,打个比如 原容量为16 当前元素的hash值为15 也就是 0000 1111
                            // 原来做hash & length - 1时 => 0 1111  0000 1111 & 0000 1111 => 0000 1111
                            // 扩容后,newLengh = 2 * length = 32
                            // hash & newLength - 1 => 0000 1111 & 0001 1111 => 0000 1111说明hash在新数组下的索引
                            // 下标未发生改变,这里我们也找到一个规律,比如原hash为31 那么在老数组中索引下标为0000 1111
                            // 在新数组的索引下标为0001 1111 => 31 => oldIndex + oldCap
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            // 如果索引下标发生改变,也就是下标为原索引下标 + oldCap
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        // 原索引下标防止在bucket中
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        // 原索引下标 + oldCap 放在bucket中
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

 

©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页