JDK 8 HashMap源码解读

开始进入HashMap前,先了解一下知识,这样才能更好的理解源码。

开始前预习

关于二叉树的知识点摘自:https://www.jianshu.com/p/bf73c8d50dc2

推荐看原文;树的相关知识只作为回顾,不会详细说明。

树:

  • 有且仅有一个特定的成为根的节点
  • 当n>1时,其余节点可分为m(m>0)个互不相交的有限集 T1、T2、…、Tn,其中每一个集合本身又是一棵树,并且称为根的子树。

此外,树的定义还需要强调以下两点:

  • n>0时根结点是唯一的,不可能存在多个根结点,数据结构中的树只能有一个根结点。

  • m>0时,子树的个数没有限制,但它们一定是互不相交的。

节点的度:

节点拥有的子树数目称为节点的度。

节点的层次:

从根节点定义起,根为第一层,根的孩子为第二层,以此类推。

树的深度:

树中节点的最大层次数称为树的深度和高度。

二叉树

特点:

  • 每个节点最多有两颗子树,所以二叉树中不存在度大于2的节点
  • 左子树和右子树是有顺序的,次序不能任意颠倒
  • 即使树中某节点只有一颗子树,也要区分它是左子树还是右子树

其实二叉树很好理解

斜树:

  • 所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树

满二叉树:

  • 在一棵二叉树中。如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树

完全二叉树:

  • 对一颗具有n个结点的二叉树按层编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树

存储结构:

  • 顺序存储结构:使用一维数组存储,并且存储的位置就是数组的下标索引,但一般适用于完全二叉树
  • 链表存储结构:节点定义有指向左孩子和右孩子的指针

二叉查找树(BST)

定义:

又称二叉排序树,具有二叉树性质,若它有左子树,字左子树上所有节点的值均小于它的根节点的值,若它的右子树不为空,则右子树上所有节点的值均大于它的根节点的值

对于上面的定义,普通的二叉树就不一样,它不像查找树这样,有大小之分。

完全平衡二叉树(AVL)

定义:

在二叉查找树的基础上,左子树和右子树的高度之差的绝对值不超过1;左子树和右子树的高度因子称之为平衡因子。

缺点:

红黑树

它是一种自平衡二叉查找树

定义:

  • 节点非红即黑
  • 根节点是黑的
  • 每个叶子节点都是黑的(空节点)
  • 从根节点到叶子节点,不会出现两个连续的红色节点
  • 对每个节点,从该节点到叶子节点的路径上包含相同数目的黑节点

理解左旋和右旋:

  • 左旋是逆时针旋转,右旋是顺时针旋转;

  • 右旋就是旋转节点以左孩子为中心,顺时针旋转,左孩子的右孩子变为旋转节点的左孩子,旋转节点变为左孩子的右孩子(好绕,看到比较好)

  • 左旋就是旋转节点以右孩子为中心,逆时针旋转,右孩子的左孩子变为旋转节点的右孩子,旋转节点变为右孩子的

在这里插入图片描述

插入的规律:

  • 父亲节点是黑色的,不进行调整 ;

  • 父亲节点是红色的:

    • 叔叔节点是红色,父亲节点+叔叔节点变黑色,祖父节点变红色,如果祖父节点上面还有节点,那么将祖父节点作为新节点去检查上面的树;

    • 叔叔节点为空,父节点,新节点在左边,祖父节点右旋;父节点,新节点在右边,祖父节点左旋;父节点在右,新节点在左,新节点右旋,祖父节点左旋,反之旋转方向相反;

    • 叔叔是黑色,父节点,新节点在左边,祖父节点右旋;父节点,新节点在右边,祖父节点左旋;父节点在右,新节点在左,新节点右旋,祖父节点左旋,反之旋转方向相反;(这条和叔叔节点是空的一样,因为所有的叶子节点(空节点)都是黑的)

对于父亲节点红色,叔叔节点不存在或者是黑的规律,文字描述过于绕,我简化为下面的:

定:x:新节点,p:父节点,pp祖父节点,以父节点p为旋转中心

  1. p右,x右,pp左旋

  2. p右,x左,x右旋(p以x为旋转中心,右旋),pp左旋

  3. p左,x左,pp右旋

  4. p左,x右,x左旋(p以x为旋转中心,右旋),pp右旋

实例检验以上规律:

  • **父亲节点是黑色的,不进行调整 **

这种情况有:

1.只有根节点
在这里插入图片描述

2.在上一次插入后,叶子节点(非空节点)为黑色

如下,第一幅图中在插入10 后,父级那一层20和40都变为黑色,或者是第二幅图中插入10后,父级和祖父级10、25和20都变色。

以定义来说:

  • 节点非红即黑 =》 符合
  • 根节点是黑的 =》 符合
  • 每个叶子节点都是黑的(空节点) =》 符合
  • 从根节点到叶子节点,不会出现两个连续的红色节点 =》 符合
  • 对每个节点,从该节点到叶子节点的路径上包含相同数目的黑节点 =》 符合

所以,这些情况下,再插入节点,都不需要调整

在这里插入图片描述

在这里插入图片描述

  • 叔叔节点是红色的,叔叔节点是红色,父亲节点+叔叔节点变黑色,祖父节点变红色,如果祖父节点上面还有节点,那么将祖父节点作为新节点去检查上面的树;

在这里插入图片描述

虽然这颗树调整完后,不是很好看,但是它完全符合红黑树的定义,配合定义再来看一遍:

  • 节点非红即黑 =》 符合
  • 根节点是黑的 =》 符合
  • 每个叶子节点都是黑的(空节点) =》 符合
  • 从根节点到叶子节点,不会出现两个连续的红色节点 =》 符合
  • 对每个节点,从该节点到叶子节点的路径上包含相同数目的黑节点 =》 符合

在这里插入图片描述

  • 父亲节点是红色,叔叔节点为空,父节点,新节点在左边,祖父节点右旋;父节点,新节点在右边,祖父节点左旋,反之旋转方向相反;父节点在右,新节点在左,新节点右旋,祖父节点左旋,反之旋转方向相反;

简单的如下面情况,在插入10或者是小于30的数,都需要调整

在这里插入图片描述

或者是下面情况,插入小于20的数,就会发生旋转,插入5发生右旋,插入15发生左旋。

在这里插入图片描述

  • 叔叔是黑色,父节点,新节点在左边,祖父节点右旋;父节点,新节点在右边,祖父节点左旋;父节点在右,新节点在左,新节点右旋,祖父节点左旋,反之旋转方向相反;(这条和叔叔节点是空的一样,因为所有的叶子节点(空节点)都是黑的)

上面图稍微修改一下,这个图比上面复杂一点,但是找到规律也是很简单的,在第一次插入5时,调整的是20为根节点的子树,当调整完后,将20作为新节点,去调整上面的树也就是红框框住的树(每次调整只关心3层);在调整完后,整颗树就比较完美了。

在这里插入图片描述

源码

红黑树源码

上边我们从原理和实例上了解了红黑树,现在从源码级别来看看他的一个流程,HashMap的插入有使用到红黑树,所以,了解了红黑树,再去看效果会更好。

static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
    // 新节点默认红色
            x.red = true;
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
                // 表示没有父亲节点,也就是根节点,设为黑色
                if ((xp = x.parent) == null) {
                    x.red = false;
                    return x;
                }
                // 父亲节点位黑色,或者是祖父节点位空,这个不需要处理,直接返回
                else if (!xp.red || (xpp = xp.parent) == null)
                    return root;
                
                // 下面的情况都是父亲节点是红色为前提
                
                // 判断父亲节点是否左节点
                if (xp == (xppl = xpp.left)) {
                    // 判断叔叔节点是不是红的
                    if ((xppr = xpp.right) != null && xppr.red) {
                        // 父亲 叔叔都是红色,就变为黑色
                        xppr.red = false;
                        xp.red = false;
                        xpp.red = true;
                        // 将祖父节点作为新节点(往上层检查,变换)
                        x = xpp;
                    }
                    // 到这里存在两种情况:1.不存在叔叔节点,2.叔叔节点是黑色
                    else {
                        // 如果新节点插入到右边
                        if (x == xp.right) {
                            // 新节点左旋
                            root = rotateLeft(root, x = xp);
                            // 左旋后,矫正变量对应的节点
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        
                        if (xp != null) {
                            // 设置xp节点颜色
                            xp.red = false;
                            if (xpp != null) {
                                // 设置xpp为红色,然后右旋
                                xpp.red = true;
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                // 父亲节点是右节点
                else {
                    // 叔叔节点红色
                    if (xppl != null && xppl.red) {
                        // 变色
                        xppl.red = false;
                        xp.red = false;
                        xpp.red = true;
                        // 递归设置
                        x = xpp;
                    }
                    // 叔叔节点黑色
                    else {
                        // 新节点是左孩子
                        if (x == xp.left) {
                            root = rotateRight(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }

左旋

 static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
     // r: p的右孩子
     // pp:p的父亲
     // rl:p的右孩子的左孩子(r的左孩子)
     // p: 要旋转的节点
            TreeNode<K,V> r, pp, rl;
            if (p != null && (r = p.right) != null) {
                // 修改p右孩子地指针
                if ((rl = p.right = r.left) != null)
                    // r的左孩子不为空,就把它的parent指针指向p
                    rl.parent = p;
                // 修改r的parent指针,指向pp
                if ((pp = r.parent = p.parent) == null)
                    // 这种情况就是pp为根节点,就把r作为根节点,并变为黑色
                    (root = r).red = false;
                // pp不是根节点,并且左孩子是p
                else if (pp.left == p)
                    // 就把r变为pp的左孩子
                    pp.left = r;
                // 除去以上情况:p为pp的右孩子
                else
                    pp.right = r;
                // 修改指针执行,完成旋转
                r.left = p;
                p.parent = r;
            }
            return root;
        }

右旋

   static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                               TreeNode<K,V> p) {
       // l: P的左孩子
       // pp:P的父亲
       // lr:p的左孩子的右孩子(l的右孩子)
       // p: 要旋转的节点
            TreeNode<K,V> l, pp, lr;
            if (p != null && (l = p.left) != null) {
                // 修改l右孩子指针
                if ((lr = p.left = l.right) != null)
                    // l的右孩子不为空,就把lr的父亲指针指向p
                    lr.parent = p;
                // 修改p的parent指针指向
                if ((pp = l.parent = p.parent) == null)
                    // 根节点设置黑色
                    (root = l).red = false;
                // 如果p为右节点
                else if (pp.right == p)
                    // 修改pp的右节点为l(p的右孩子)
                    pp.right = l;
                // 非上述情况:p为左节点
                else
                    // 修改pp的左节点为l(p的左孩子)
                    pp.left = l;
                // 修改指针,完成旋转
                l.right = p;
                p.parent = l;
            }
            return root;
        }

下面这种情况是:叔叔节点是红色,父亲节点+叔叔节点变黑色,祖父节点变红色,如果祖父节点上面还有节点,那么将祖父节点作为新节点去检查上面的树;

在插入新节点后,需要对红黑树进行调整,在调整的最后,x = xpp这个的意思是:从我们插入节点开始进行检查是否符合红黑树定义,每次的检查涉及到插入节点x、父节点xp、祖父节点xpp,相当于一颗大的树里的一个子树,所以需要递归的方法去调整整颗树。

在这里插入图片描述

这种情况是,不存在叔叔节点,或者叔叔节点是黑色的。

他就包含了以下规律:

  • 叔叔节点为空,父节点,新节点在左边,祖父节点右旋;父节点,新节点在右边,祖父节点左旋;父节点在右,新节点在左,新节点右旋,祖父节点左旋,反之旋转方向相反;
  • 叔叔是黑色,父节点,新节点在左边,祖父节点右旋;父节点,新节点在右边,祖父节点左旋;父节点在右,新节点在左,新节点右旋,祖父节点左旋,反之旋转方向相反;(这条和叔叔节点是空的一样,因为所有的叶子节点(空节点)都是黑的)
    在这里插入图片描述

先以简单例子走流程,分析他的调整过程。

假设一:父亲节点红色,不存在叔叔节点

第一次将 r左旋,调换pr的位置。

在这里插入图片描述

在旋转完后,需要把各变量对应的节点重新对应设置,然后修改xpp颜色,然后进入第二处旋转。

第二次将pp右旋,调整ppr的右孩子并变色;

在这里插入图片描述

右旋和左旋一样,修改指针指向,调整节点树顺序,到这才完成红黑树的调整。

在这里插入图片描述

假设二:父亲节点红色,在左边,叔叔节点黑色,插入新节点也在左边

以这个图为例,将红框中的部分拆出来。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

HashMap 源码

JDK7和8有这样一些区别:

  1. 在hash上的计算,8 的没有7 的复杂,原因可能就是在8里面引入了红黑树,已经将插入读取的效率提高了,再在下标上下功夫没有多大用处了。
final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

jdk8:

 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  1. 8里的内部数组数组类型是Node,7里面是Entry

  2. 8里面链表是尾插法,7是头插法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
    // 如果空的,新建节点;这里的下标计算和jdk7的一样取模
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            // 找相同的节点
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            // 红黑树
            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;
                    }
                    // 遍历到当前级退出
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

情况一:插入前还是链表

红黑树是改变链表的插入查询效率提升,这里在树化增加判断,利用扩容的方式使链表长度变短,提升效率,当长度大于64后,效率提升不大,就采用红黑树树。

final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
    // null,或者长度< 64 不会树化;效率问题
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            // 红黑树的节点:包含parent,left,right,prev,它是NOde的子类,同时也包含Node的属性,
            // 
            TreeNode<K,V> hd = null, tl = null;
            // 从第一个元素遍历
            do {
                // 修改节点为红黑树的节点对象
                TreeNode<K,V> p = replacementTreeNode(e, null);
                // 第一个元素进来,hd、tl 为P
                // 第二次进来tl != null
                if (tl == null)
                    hd = p;
                // 循环第二次开始都走这里
                else {
                    // 形成双向列表;
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                // 第一个节点不为空,
                hd.treeify(tab);
        }
    }

balanceInsertion方法就是在红黑树哪里讲过,

final void treeify(Node<K,V>[] tab) {
            TreeNode<K,V> root = null;
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null;
                if (root == null) {
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
                    // 表示新节点的
                    K k = x.key;
                    // 新节点的hash
                    int h = x.hash;
                    // key 的class类型
                    Class<?> kc = null;
                    // 从根节点开始
                    for (TreeNode<K,V> p = root;;) {
                        int dir, ph;
                        K pk = p.key;
                        // 根据树的概念,大的右边,小的左边进行插入,那么这里也是判断下一个几点应该是在那一边
                        if ((ph = p.hash) > h)
                            // dir -1 往右边走,1 则往左
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        else if ((kc == null &&
                                  // kc = null,才会进行赋值;
                                  (kc = comparableClassFor(k)) == null) ||
                                 // kc != null空的情况进入这个判断;判断k 和pk是否相等(新节点和根节点的key是否相等)
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            // 重新计算dir
                            dir = tieBreakOrder(k, pk);

                        TreeNode<K,V> xp = p;
                        // dir <= 0 ,插入到p的左边;这里p.left 赋值给了p,是遍历left下的树
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;
                            // 调整红黑树;
                            root = balanceInsertion(root, x);
                            break;
                        }
                    }
                }
            }
    // 移动root
            moveRootToFront(tab, root);
        }

这里,如果x实现的是Comparable接口就返回其类型,如果不是就返回空

 static Class<?> comparableClassFor(Object x) {
        if (x instanceof Comparable) {
            Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
            if ((c = x.getClass()) == String.class) // bypass checks
                return c;
            if ((ts = c.getGenericInterfaces()) != null) {
                for (int i = 0; i < ts.length; ++i) {
                    if (((t = ts[i]) instanceof ParameterizedType) &&
                        ((p = (ParameterizedType)t).getRawType() ==
                         Comparable.class) &&
                        (as = p.getActualTypeArguments()) != null &&
                        as.length == 1 && as[0] == c) // type arg is c
                        return c;
                }
            }
        }
        return null;
    }

计算dir值

  static int tieBreakOrder(Object a, Object b) {
            int d;
            if (a == null || b == null ||
                // 比较classname 
                (d = a.getClass().getName().
                 compareTo(b.getClass().getName())) == 0)
                // 比较hashcode
                d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
                     -1 : 1);
            return d;
        }

移动root节点

 static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
            int n;
            if (root != null && tab != null && (n = tab.length) > 0) {
                // 计算位置
                int index = (n - 1) & root.hash;
                // 这个就是链表的头结点
                TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
                // root不是头结点的的情况下
                if (root != first) {
                    Node<K,V> rn;
                    // 将数组中的位置换成root节点
                    tab[index] = root;
                    // root的前置节点rp
                    TreeNode<K,V> rp = root.prev;
                    
                    if ((rn = root.next) != null)
                        // 把root节点下一个节点的前节点指向根节点的上一个
                        ((TreeNode<K,V>)rn).prev = rp;
                    if (rp != null)
                        // root前一个节点到 下一个节点指向root节点的上一个
                        rp.next = rn;
                    if (first != null)
                        // 把root放到第一个节点的前面
                        first.prev = root;
                    root.next = first;
                    root.prev = null;
                }
                assert checkInvariants(root);
            }
        }

上面这代码的意思画图表示下更清晰(我曹 你TM画个锤子):

其实它现在是颗红黑树,但是他有链表结构,

在这里插入图片描述

情况二:插入前是红黑树;

其里面很多判断方法和上面的一样

final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                       int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
    // 获取到根节点
            TreeNode<K,V> root = (parent != null) ? root() : this;
    // 遍历
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                // 插入左边和右边的判断
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
               // 找到hash值相同的,可能值也相同
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                // hash值相同,且地址不同或者值又或者equals不等
                else if ((kc == null &&
                          // 获取k的类型,赋值kc
                          (kc = comparableClassFor(k)) == null) ||
                         // 只有kc != null才会进入(k实现comparable)
                         // 判断kc的类型不相等才会返回0,不然返回k和pk的比较结果
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    // 进入这里,表示,k实现comparable,或者k和pk相等
                    if (!searched) {
                        TreeNode<K,V> q, ch;
                        searched = true;
                        if (((ch = p.left) != null &&
                             (q = ch.find(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.find(h, k, kc)) != null))
                            return q;
                    }
                    dir = tieBreakOrder(k, pk);
                }
				// 插入节点
                TreeNode<K,V> xp = p;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    Node<K,V> xpn = xp.next;
                    TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    xp.next = x;
                    x.parent = x.prev = xp;
                    if (xpn != null)
                        ((TreeNode<K,V>)xpn).prev = x;
                    // 调整红黑树并移动root节点为头结点
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
            }
        }

JDK7里面的扩容多了一个判断条件:索引位置是否是空的;

还有就是8里面的,他会将链表和红黑树分为低位和高位的链表进行转移,比起7的比较复杂。

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) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 旧数组长度2倍且大于等于默认长度
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                // 阈值扩大2倍
                newThr = oldThr << 1; // double threshold
        }
    // 旧数组长度为0,不变
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            // 默认值
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
    // 旧数组不为空
        if (oldTab != null) {
            // 遍历;j为数组里的元素索引
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                // j位置的元素不为空,存在树或链表
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    // 下一个元素是空的,就直接把这个元素放到新数组中
                    if (e.next == null)
                        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;
                            // 与原来的数组长度&,得到的结果只有高位和低位
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    // 设置头结点
                                    loHead = e;
                                else
                                    loTail.next = e;
                                // 设置尾节点
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            // 低位的链表的尾结点赋值null
                            loTail.next = null;
                            // 移动低位链表到新数组
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            // 高位链表的尾结点赋值null
                            hiTail.next = null;
                            // 移动高位链表到新数组
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

// tab 新数组,index 旧数组索引, bit 就数组容量
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
    // 当前节点,头结点
            TreeNode<K,V> b = this;
            // Relink into lo and hi lists, preserving order
            TreeNode<K,V> loHead = null, loTail = null;
            TreeNode<K,V> hiHead = null, hiTail = null;
            int lc = 0, hc = 0;
    // 遍历链表
            for (TreeNode<K,V> e = b, next; e != null; e = next) {
                next = (TreeNode<K,V>)e.next;
                e.next = null;
                // 一样的分出高位链表和低位链表
                if ((e.hash & bit) == 0) {
                    if ((e.prev = loTail) == null)
                        loHead = e;
                    else
                        loTail.next = e;
                    loTail = e;
                    ++lc;
                }
                else {
                    if ((e.prev = hiTail) == null)
                        hiHead = e;
                    else
                        hiTail.next = e;
                    hiTail = e;
                    ++hc;
                }
            }

    // 低位不为null
            if (loHead != null) {
                // 低位链表的长度小于指定阈值就将树变为链表(解树化)
                if (lc <= UNTREEIFY_THRESHOLD)
                    tab[index] = loHead.untreeify(map);
                else {
                    // 低位链表放到新数组位置
                    tab[index] = loHead;
                    // 如果高位不为null,就重新树化低位链表
                    // 这里的意思是,本身一颗红黑树,被分成了高低位链表,然后移动到新数组不同位置,所以需要重新树化
                    if (hiHead != null) // (else is already treeified)
                        loHead.treeify(tab);
                }
            }
            if (hiHead != null) {
                if (hc <= UNTREEIFY_THRESHOLD)
                    tab[index + bit] = hiHead.untreeify(map);
                else {
                    tab[index + bit] = hiHead;
                    if (loHead != null)
                        hiHead.treeify(tab);
                }
            }
        }
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页