前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JDK 8 HashMap源码解读

JDK 8 HashMap源码解读

作者头像
用针戳左手中指指头
发布2021-01-29 11:10:14
2860
发布2021-01-29 11:10:14
举报
文章被收录于专栏:学习计划

开始进入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的插入有使用到红黑树,所以,了解了红黑树,再去看效果会更好。

代码语言:javascript
复制
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);
                            }
                        }
                    }
                }
            }
        }

左旋

代码语言:javascript
复制
 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;
        }

右旋

代码语言:javascript
复制
   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里面引入了红黑树,已经将插入读取的效率提高了,再在下标上下功夫没有多大用处了。
代码语言:javascript
复制
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:

代码语言:javascript
复制
 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是头插法
代码语言:javascript
复制
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后,效率提升不大,就采用红黑树树。

代码语言:javascript
复制
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方法就是在红黑树哪里讲过,

代码语言:javascript
复制
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接口就返回其类型,如果不是就返回空

代码语言:javascript
复制
 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值

代码语言:javascript
复制
  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节点

代码语言:javascript
复制
 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画个锤子):

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

在这里插入图片描述
在这里插入图片描述

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

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

代码语言:javascript
复制
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的比较复杂。

代码语言:javascript
复制
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;
    }
代码语言:javascript
复制
// 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/08/01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开始前预习
      • 二叉树
        • 二叉查找树(BST)
          • 完全平衡二叉树(AVL)
          • 红黑树
          • 源码
            • 红黑树源码
              • HashMap 源码
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档