JDK1.8深入HashMap详解

HashMap

 

常量、变量

//初始Node[] 大小为16

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

 

//Node[] 最大值为2的30次方

static final int MAXIMUM_CAPACITY = 1 << 30;

 

//默认加载因子

static final float DEFAULT_LOAD_FACTOR = 0.75f;

 

//树化阈值

static final int TREEIFY_THRESHOLD = 8;

 

//退为链表阈值

static final int UNTREEIFY_THRESHOLD = 6;

 

//HashMap的基本单位(后续我们会说成一个个桶)

transient Node[] table;

 

 

transient Set> entrySet;

 

//HashMap以存储数据的数量

transient int size;

 

//记录hashmap的修改次数,应用于fail-fast机制

transient int modCount;

 

//扩容阈值

int threshold;

 

//加载因子

final float loadFactor;


 

Node内部类其实现了Entry接口

Node重写了hashCode与equals方法,具体自己看源码

 

4个构造方法:

    //如果入参initialCapacity大于MAXIMUM_CAPACITY(2的30次方)则让其值为2的30次方

    如果入参initialCapacity与loadFactory入参不正确则抛出异常

    1、public HashMap(int initialCapacity, float loadFactor) {

        if (initialCapacity < 0)

            throw new IllegalArgumentException("Illegal initial capacity: " +

                                               initialCapacity);

        if (initialCapacity > MAXIMUM_CAPACITY)

            initialCapacity = MAXIMUM_CAPACITY;

        if (loadFactor <= 0 || Float.isNaN(loadFactor))

            throw new IllegalArgumentException("Illegal load factor: " +

                                               loadFactor);

        this.loadFactor = loadFactor;

        this.threshold = tableSizeFor(initialCapacity);

    }

 2、public HashMap(int initialCapacity) {

        this(initialCapacity, DEFAULT_LOAD_FACTOR);

    }

 3、public HashMap() {

        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted

    }

 //牵扯太多 后续再说

 4、public HashMap(Mapextends K, ? extends V> m) {

        this.loadFactor = DEFAULT_LOAD_FACTOR;

        putMapEntries(m, false);

    }

 

 

  

 


 

请看下面一段代码

    // 声明了一个Hash集合叫map,此时我们只是给map的加载因子赋予了默认值。   

     Hash map = new HashMap<>();

   //我们put一个String类型的对象进入map

    map.put("1","test1");

请看put源码:

       

     public V put(K key, V value) {

        return putVal(hash(key), key, value, false, true);

    }

   final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

                   boolean evict) {

        Node[] tab; Node p; int n, i;//tab用来接当前map的table数组,p是table[]对应位置的元素

        if ((tab = table) == null || (n = tab.length) == 0)

            n = (tab = resize()).length;//如果map为空此时做出扩容操作

        if ((p = tab[i = (n - 1) & hash]) == null)//(n-1)&hash计算出当前hash值对应table[]数组中哪个位置,如果当前位置为空则直接放在这里

            tab[i] = newNode(hash, key, value, null);

        else {//如果当前位置不为空

            Node e; K k;//k表示当前节点的key值,e用来记录旧节点(覆盖情况的出现)

           //每次判断桶中的第一个元素即table[i],得到覆盖或者保持旧值取决于onlyIfAbsent

            if (p.hash == hash &&

             ((k = p.key) == key || (key != null && key.equals(k))))

                e = p;

            else  if (p instanceof TreeNode)

                e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);

            else {

                for (int binCount = 0; ; ++binCount) {//遍历桶中的链表,e用来记录p节点的next

                    if ((e = p.next) == null) {//只要遍历到p的下一个节点为空就直接连接到p的下一个节点

                        p.next = newNode(hash, key, value, null);

                        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;//如果遍历到key相同的节点就跳出循环

                    p = e;//往链表下一个节点移动

                }

            }

            if (e != null) { // existing mapping for key//put进的key值与链表上某一节点的key值相同

                V oldValue = e.value;

                if (!onlyIfAbsent || oldValue == null)

                    e.value = value;//赋予新值

                afterNodeAccess(e);

                return oldValue//返回旧值

            }

        }

        ++modCount;//应用于fast-

        if (++size > threshold)

            resize();

        afterNodeInsertion(evict);

        return null;

    }


依次解释putVal中hash()、resize()、putTreeVal()

hash()

    static final int hash(Object key) {

        int h;

        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

    }

    返回一个int型基本数据。

    1、如果key是null那么返回0,说明HashMap是可以把null作为Key传入的,

    2、如果key不是null那么返回的是,key的hash值的前16位异或后16位,因为一个int是4个字节(32位二进制)。

    充分利用了其高位数据,使hash值更加散乱。

 

来看resize()--->HashMap的扩容机制

    final Node[] resize() {

        Node[] oldTab = table;//记录旧数组,待会需要把旧数组的值装入新数组中

        int oldCap = (oldTab == null) ? 0 : oldTab.length;//oldCap记录原数组大小

        int oldThr = threshold;//oldThr记录原数组的扩容阈值

        int newCap, newThr = 0;//newCap记录新的数组大小、newThr记录新的数组的扩容阈值

        if (oldCap > 0) {    //原数组不为空

            if (oldCap >= MAXIMUM_CAPACITY) {//原数组大小大于MAXIMUM_CAPACITY,则把threshold赋值为2^31-1

                threshold = Integer.MAX_VALUE;

                return oldTab;

            }

            //newCap = oldCap*2 之后如果小于MAXIMUM_CAPACITY且oldCap大于16(说明构造或经过了扩容),那么新阈值*2

            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&

                     oldCap >= DEFAULT_INITIAL_CAPACITY)

                newThr = oldThr << 1; 

        }

        else if (oldThr > 0) 

            newCap = oldThr;

        else {               //初始化hashmap

            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[] newTab = (Node[])new Node[newCap];//新建了一个大小为newCap的Node数组

        table = newTab;

        if (oldTab != null) {//既然我们已经有了新的Node[](空的)、新的阈值,那么我们只差copy新数组的值了

            for (int j = 0; j < oldCap; ++j) {//遍历

                Node e;//用来暂时存储原数组中的元素

                if ((e = oldTab[j]) != null) {//先把非空旧节点赋值给e,然后把旧节点赋予空值(准备gc)

                    oldTab[j] = null;        

                    if (e.next == null)//如果当前桶只有一个元素(而非链表结构)

                        newTab[e.hash & (newCap - 1)] = e;//找出e节点在新table[]中的位置,并赋值

                    else if (e instanceof TreeNode)//如果e节点是树节点(说明桶中是红黑树了),须知!只有树化时才会把Node转换为ThreeNode

                        ((TreeNode)e).split(this, newTab, j, oldCap);//

                    else {//说明是链表结构

                        Node loHead = null, loTail = null;//低位链头链尾

                        Node hiHead = null, hiTail = null;//高位链头链尾

                        Node next;

                        do {

                            next = e.next;

                            //我们可以知道的是CAPACITY一定是2的N次方,所以如果相与为0那么说明e的哈希值一定小于数组长度。我们把这一部分节点

                              叫做低位链表(0到oldCap-1)。剩下的叫高位链表(oldCap到newCap-1)。

                            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) {

                            loTail.next = null;

                            newTab[j] = loHead;

                        }

                        if (hiTail != null) {

                            hiTail.next = null;

                            newTab[j + oldCap] = hiHead;

                        }//这两条if说明了一个事实,在原数组中的一个位置,会被分配到新数组中的两个位置,例如table[6](table为2^3次方)上的链表,扩容以后一部分在table[6],一部

                                  分在table[14]。

                    }

                }

            }

        }

        return newTab;

    }

 

可能resize有点长看的会有点晕,让我们来整理一下思路:

有①②③三个条件组依次判断

    

    ①、条件:原数组不为空

        1)原数组大小大于MAXIMUN_CAPACITY,修改threshold为Integer.MAXIMUN(即2^31-1)

        2)新数组大小为原数组的两倍且扩容完后的CAPACITY必须小于MAXIMUN_CAPACITY,而且原数组大小必须大于16。那么才给threshold*2

    ①、条件:原数组不为空、原阈值大于0

            直接newThr = oldThr

    ①、条件:原数组为空、或者原阈值小于等于0(说明是调用了默认的构造器)

            newCap = DEFAULT_INITIAL_CAPACITY;

            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

 

    ②、条件:如果新阈值为零(这一步是为了防止在之前的if判断的情况下newThr没有被赋值)

        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?

                      (int)ft : Integer.MAX_VALUE);

 

        新数组容量小于最大值,且新阈值小于最大值,则让newThr = newCap*loadFactor 。否则就让其为Integer.MAX_VALUE。其实跟1、略相似

    ③、条件:原数组内容是否为空

         不为空则要把原数组的内容放在新数组中,且原数组准备gc

         for(int j=0; j

         每次用Node记录table[j]然后让oldTab[j] = null;

         接下来一组判断

        1)条件:如果e的next为空(说明桶中只有一个元素)

            那么newTab[e.hash & (newCap - 1)] = e;

        2)条件:桶中不止一个元素,e是树节点(说明这个桶中是红黑树结构)

            ((TreeNode)e).split(this, newTab, j, oldCap);

        3)条件:桶中不止一个元素,且不是红黑树结构,那么就是单链表结构了

            把单链表分为高位链表低位链表两条链表

            然后判断如果高位(低位)链表不为空则把低位链表放在当前桶中,高位链表放在当(前桶的位置+oldCap)。

 


看完树化看红黑树的putVal()

treeifyBin()--->树化前的准备工作,把单链表变成双链表

    final void treeifyBin(Node[] tab, int hash) {

        int n, index; Node e;

        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)//树化前的最后一步判断,如果tab[]的长度小于最小树化值(64),则扩容。

            resize();

        else if ((e = tab[index = (n - 1) & hash]) != null) {

            TreeNode hd = null, tl = null;//hd指向链表头节点,tl始终指向最新的新增节点。

            do {

                TreeNode p = replacementTreeNode(e, null);//replacementTreeNode返回一个TreeNode,它的next节点是第二个参数

                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);//树化的终极细节

        }

    }

树化之前必须了解红黑树!

为什么会出现红黑树?

   红黑树是基于二叉搜索树的。

    二叉搜索树可以快速的找出一个给定关键字的数据,并且可以快速插入和删除数据项。但是二叉搜索树有一个很麻烦的问题,

    如果插入的数据是随机的那么执行的效果会很好,可是如果插入的数据是有序、反序的数据此时二叉搜索树会退化成一个线性

    链表,那么它的快速查找、插入、删除指定数据项的功能就丧失了。


 

红黑树节点特性:

   1、每个节点只有红、黑两种颜色

    

    2、根节点是黑色

    

    3、每个叶子结点( NIL或者空节点 )是黑色

 

    4、红色节点的子节点必须是黑色的( 不存在父节点和其任意一个子节点都为红色节点

 

    5、从任一节点到其每个子节点所有的路径包含相同数据的黑色节点( 相同黑色高度 )


树节点

     parent:父节点

    left:左孩子节点

    right:右孩子节点

    prev:双链表(指向上一个几点)

treeify()--->树化细节

需要注意的是,树化只是树化当前桶中的双链表成红黑树,并非所有table[]节点

    final void treeify(Node[] tab) {

            TreeNode root = null;//用于记录红黑树根节点

            for (TreeNode x = this, next; x != null; x = next) {//x等于调用了该方法的对象

                next = (TreeNode)x.next;

                x.left = x.right = null;

                if (root == null) {//如果是树化的初始阶段,则由调用该方法的对象(双链表的链头)作为根节点。

                    x.parent = null;

                    x.red = false;//根节点必须为黑色

                    root = x;

                }

                else {

                    K k = x.key;

                    int h = x.hash;

                    Class kc = null;//记录待插入节点x的运行时类型

                    for (TreeNode p = root;;) {//从根节点开始循环遍历找到合适的插入位置

                        int dir, ph;//dir为正:插入位置为左节点、dir为负:插入节点为右节点。ph代表当前遍历节点的hash值

                        K pk = p.key;

                        if ((ph = p.hash) > h)

                            dir = -1;

                        else if (ph < h)

                            dir = 1;

                       //若hash值相等则判断K类型是否实现了Comparable接口,实现了则由Comparable的compareTo方法比较

                        else if ((kc == null &&

                                  (kc = comparableClassFor(k)) == null) ||

                                 (dir = compareComparables(kc, k, pk)) == 0)

                            dir = tieBreakOrder(k, pk);

                        TreeNode xp = p;

                       //遍历节点p的左(右)子树为空直接插入,并且让p往下一层走一步(用于下一次循环)。

                        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;

                        }

                    }

                }

            }

            moveRootToFront(tab, root);//树化完成时把根节点变为桶中的第一个元素

        }


 

treeify中的comparableClassFor方法:

 

//如果x对象的类是T,且实现了Comparable那么返回类型T,否则返回null

static Class comparableClassFor(Object x) {

        if (x instanceof Comparable) {//首先判断是否实现了Comparable接口,没实现就直接返回null

            Class c; Type[] ts, as; Type t; ParameterizedType p;

            //判断是否为String类,若是则直接返回Class

            //public final class String implements java.io.Serializable, Comparable<String>

            //可以看到String是实现了Comparable接口的

            if ((c = x.getClass()) == String.class)

                return c;

            if ((ts = c.getGenericInterfaces()) != null) {

                for (int i = 0; i < ts.length; ++i) {

              //如果该接口是泛型类型接口

              //且原生类型为Comparable类

              //且泛型参数只有一个,并且是c类型的  (类似于“c 类名 implements Comparable”)

                    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;

    }


 

balanceInsertion()--->每次插入节点之后红黑树进行平衡

在了解怎么平衡之前请让这幅图在脑海中有一定影响,对于代码的理解帮助非常大

balanceInsertion细节

JDK1.8深入HashMap详解_第1张图片

JDK1.8深入HashMap详解_第2张图片

JDK1.8深入HashMap详解_第3张图片

代码部分:

static TreeNode balanceInsertion(TreeNode root,

                                                    TreeNode x) {

            x.red = true;//可以看出每次插入的新节点都为红色

               //循环只有2个return跳出

            for (TreeNode xp, xpp, xppl, xppr;;){//xp:x的父亲、xpp:x的祖父、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;//让下次循环节点从祖父节点开始

                    }

                    else {//当叔叔节点不存在、或者叔叔节点为黑色的时候

                        if (x == xp.right) {//当插入节点为父节点的右孩子的时候

                            root = rotateLeft(root, x = xp);//父节点左旋

                            xpp = (xp = x.parent) == null ? null : xp.parent;

                        }

                        if (xp != null) {//若旋转之后的父节点不为空则变黑色

                            xp.red = false;

                            if (xpp != null) {//若祖父节点不为空则变为红色,之后祖父节点右旋

                                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);

                            }

                        }

                    }

                }

            }

        }

 

moveRootToFront()--->把平衡了的红黑树的根节点放入桶中的第一个元素

    static void moveRootToFront(Node[] tab, TreeNode root) {

            int n;

            if (root != null && tab != null && (n = tab.length) > 0) {//健壮性判断

                //这里强调一下为什么index会等于树化的桶对应的index,因为双链表的位置也是(n-1)&hash。所以链表上的每个节点的

                        hash值&(n-1)的值都会相同,所以不管链表上哪个节点是平衡后的红黑树的根节点,都会在原桶之中。

                int index = (n - 1) & root.hash;

                //需要明确的是,我们树化之后双链表的prev、next指向是没有改变的,只不过修改了TreeNode的parent、left、right

                    //所以即使平衡了红黑树之后,根节点很大可能不会是双链表的链头。

                TreeNode first = (TreeNode)tab[index];//记录当前桶中第一个节点(双链表的首节点)

                if (root != first) {//如果根节点不是双链表的表头

                    Node rn;

                    tab[index] = root;//桶中的首元素直接指向了红黑树的根节点

                    TreeNode rp = root.prev;

                    if ((rn = root.next) != null)//这三个if请看如下图

                        ((TreeNode)rn).prev = rp;

                    if (rp != null)

                        rp.next = rn;

                    if (first != null)

                        first.prev = root;

                    root.next = first;

                    root.prev = null;

                }

                assert checkInvariants(root);//检测是否符合红黑树

            }

        }

JDK1.8深入HashMap详解_第4张图片

 

右旋

JDK1.8深入HashMap详解_第5张图片

实现代码:( 手动自己敲出来的 )

    static TreeNode rotateRight( TreeNode root , TreeNode p ){

        TreeNode lr,l,pp;//lr是p节点左孩子、l是p的左节点、pp是p的父节点

        if( p != null && ( l = p.left )!=null ){//先判断p节点为不为空,不为空则判断p节点的左节点为不为空,如果不为空则进入接下来的判断

            if( (lr = p.left = l.right) != null )//首先把lr指向p节点的左节点的右节点,如果这个时候lr不为空的话,那么久把p节点作为lr的父节点

                    lr.parent = p;

            if( (pp = l.parent = p.parent) == null )//首先把p节点与l节点指向pp节点(作为他们的父节点),然后如果父节点不存在那么左节点将会变为根节点

                    (root = l).red = false;

            else if( pp.right = p )//剩下的判断是说如果pp节点不为空(pp肯定指向了旋转之前该子树的根节点,判断是pp的左节点是p,还是右节点

                                     是p),那么旋转之后必定该子树的根节点必须是pp节点的一个左(右)节点。

                    pp.right = l;

            else

                    pp.left = l;

            l.right = p;//因为旋转之后子节点父节点倒置了,所以要调整一下关系

            p.parent = l;

        }       

        return root;

    }


OK我们现在学习红黑树的插入与删除

红黑树的插入

      final TreeNode putTreeVal(HashMap map, Node[] tab,

                                       int h, K k, V v) {

            Class kc = null;

            boolean searched = false;

            TreeNode root = (parent != null) ? root() : this;//获得根节点用于遍历

            for (TreeNode p = root;;) {//p为当前循环操作的节点

                int dir, ph; K pk;//dir的值影响着我们是往左还是右接着遍历红黑树

                if ((ph = p.hash) > h)

                    dir = -1;

                else if (ph < h)

                    dir = 1;

                else if ((pk = p.key) == k || (k != null && k.equals(pk)))//hash、key相同,覆盖情况的出现所以返回

                    return p;

                else if ((kc == null &&

                          (kc = comparableClassFor(k)) == null) ||

                         (dir = compareComparables(kc, k, pk)) == 0) {//hash相同、key不相同,用compareTo比较key值大小,相同则

                    if (!searched) {

                        TreeNode 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;//如果查找结果不为空(p!=null)则返回查找到的节点,交给putVal方法去覆盖

                    }

                    dir = tieBreakOrder(k, pk);

                }

                TreeNode xp = p;

                if ((p = (dir <= 0) ? p.left : p.right) == null) {//如果p节点坐在位置为空则直接进行插入

                    Node xpn = xp.next;    

                    TreeNode x = map.newTreeNode(h, k, v, xpn);//生成一个新节点,其next指向spn

                    if (dir <= 0)

                        xp.left = x;

                    else

                        xp.right = x;

                    xp.next = x;

                    x.parent = x.prev = xp;

                          //此处仔细说明一下,为什么要生成xpn这个节点,我们必须要考虑的一点是桶中是一种带有双向链表的红黑树结构

                            所以当我们插入一个节点的时候必须要考虑双链表的插入形式,即xp.next=x,x.next=xpn,xpn.prev=x,反正就是双链表中插入一个节点该做的步骤

                    if (xpn != null)

                        ((TreeNode)xpn).prev = x;

                    moveRootToFront(tab, balanceInsertion(root, x));//先平衡红黑树再把根节点置为桶中的第一个元素

                    return null;

                }

            }

        }


root()方法--->返回根节点

    final TreeNode root() {//如果父节点不为空就一直往上走,直到找到根节点

            for (TreeNode r = this, p;;) {//r记录当前循环执行节点,p记录r的父节点(用于下一次循环使用)

                if ((p = r.parent) == null)//父节点为空时跳出循环返回根节点

                    return r;

                r = p;//往上寻找

            }

        }

getTreeNode()方法--->底层调用find()方法    

final TreeNode getTreeNode(int h, Object k) {

            return ((parent != null) ? root() : this).find(h, k, null);

        }

 

 

find()方法--->Finds the node starting at root p with the given hash and key.

                    //find方法:在当前节点及其子孙节点中找到对应key值得节点,并返回

        final TreeNode find(int h, Object k, Class kc) {

            TreeNode p = this;//用p记录当前节点

            do {

                int ph, dir; K pk;

                TreeNode pl = p.left, pr = p.right, q;

                if ((ph = p.hash) > h)//如果当前节点的hash值比传入的hash值大,那么下次比较将由其左节点与传入的hash值比较

                    p = pl;

                else if (ph < h)

                    p = pr;

                else if ((pk = p.key) == k || (k != null && k.equals(pk)))//如果hash值相等,且key相等则表示找到了

                    return p;

                else if (pl == null)//hash值相、key不相同,且左孩子为空则进行下一轮循环

                    p = pr;

                else if (pr == null)

                    p = pl;

                else if ((kc != null ||

                          (kc = comparableClassFor(k)) != null) &&

                         (dir = compareComparables(kc, k, pk)) != 0)//如果左右孩子都不为空,一路下来只能用compareTo比较了,不相同p就可以进行下一路循环

                    p = (dir < 0) ? pl : pr;

                else if ((q = pr.find(h, k, kc)) != null)//如果K没有实现Comparable接口或者比较之后还是相同,只能递归调用find方法寻找右子树了

                    return q;

                else//如果右子树还是没有找到对应的节点,只能从左子树开始寻找

                    p = pl;

            } while (p != null);

            return null;//找到最后没有找到对应的节点则返回null

     }


红黑树的删除操作

    public V remove(Object key) {//返回删除节点的value值

        Node e;

        return (e = removeNode(hash(key), key, null, false, true)) == null ?

            null : e.value;

    }

    /**

     * Implements Map.remove and related methods

     *

     * @param hash hash for key

     * @param key the key

     * @param value the value to match if matchValue, else ignored

     * @param matchValue if true only remove if value is equal

     * @param movable if false do not move other nodes while removing

     * @return the node, or null if none

     */

   //先找待删除节点,最后判断若找到则删除节点,删除成功则返回删除节点,失败则返回null

    final Node removeNode(int hash, Object key, Object value,

                               boolean matchValue, boolean movable) {

        Node[] tab; Node p; int n, index;

        if ((tab = table) != null && (n = tab.length) > 0 &&

            (p = tab[index = (n - 1) & hash]) != null) {

            Node node = null, e; K k; V v;//node用来记录找到匹配的待删除节点

            if (p.hash == hash &&

                ((k = p.key) == key || (key != null && key.equals(k))))//桶中第一个元素就被匹配到相同的key值

                node = p;

            else if ((e = p.next) != null) {//如果桶中第一个元素没有被匹配到,且桶中不止一个元素的话

                if (p instanceof TreeNode)//如果桶中是红黑树结构(一定要牢记只有树化时才会生成TreeNode节点)

                    node = ((TreeNode)p).getTreeNode(hash, key);

                else {//桶中是单链表结构

                    do {//循环遍历链表,找到就跳出循环,或者找到链尾都没有找到(找到链尾时p指向的是链表中最后一个节点)

                        if (e.hash == hash &&

                            ((k = e.key) == key ||

                             (key != null && key.equals(k)))) {

                            node = e;

                            break;

                        }

                        p = e;

                    } while ((e = e.next) != null);

                }

            }

            //这才是删除的步骤,之前是找到待删除节点

            //如果matchValue为true则要判断value是否相等,如果不相同则方法返回null。

                //链表的删除直接把指向该节点的链条不指向待删除节点就行

            if (node != null && (!matchValue || (v = node.value) == value ||

                                 (value != null && value.equals(v)))) {

                if (node instanceof TreeNode)//如果匹配到的待删除节点为红黑树节点,调用红黑树的删除方法

                    ((TreeNode)node).removeTreeNode(this, tab, movable);

                else if (node == p)//说明匹配的待删除节点是桶中第一个节点,则要把单链表中第二个节点放到桶中第一个元素

                    tab[index] = node.next;

                else //前面提过了,只要是在链表遍历时候删除(链头已经匹配失败了)的时候p节点是始终跟在待删除节点的后一个节点,所以删除完成之后要把上一个节点的next指向删除节点的next

                    p.next = node.next;

                ++modCount;//修改次数加一(用于fail-fast机制)

                --size;//map的大小减一

                afterNodeRemoval(node);

                return node;//返回已删除节点

            }

        }

        return null;

    }


深入拓展环节:fast-fail(快速失败机制)

    为什么会有fast-fail机制?

    如果HashMap在遍历的时候有另外一个线程对其进行了修改,那么会造成严重的后果。

    首先看一个内部抽象类

    abstract class HashIterator {

        Node next;        // next entry to return 下一个节点

        Node current;     // current entry 当前节点

        int expectedModCount;  // 被赋予开始遍历时HashMap的modCount、用于fast-fail机制

        int index;             // current slot 遍历Node table[] 的下标

        HashIterator() {

            expectedModCount = modCount;//把当前HashMap的modCount赋值给内部类中的期望修改次数expectedModCount

            Node[] t = table;

            current = next = null;

            index = 0;

            if (t != null && size > 0) { // advance to first entry

                do {} while (index < t.length && (next = t[index++]) == null);//迅速找到第一个非空节点,因为HashMap的插入并非有序的

            }

        }

        public final boolean hasNext() {

            return next != null;

        }

        final Node nextNode() {

            Node[] t;

            Node e = next;

            if (modCount != expectedModCount)//首先就是检查在开始循环时被赋予的期望修改次数跟当前HashMap的修改次数是否相同

                throw new ConcurrentModificationException();

            if (e == null)

                throw new NoSuchElementException();

            if ((next = (current = e).next) == null && (t = table) != null) {

                do {} while (index < t.length && (next = t[index++]) == null);

            }

            return e;

        }

        public final void remove() {

            Node p = current;

            if (p == null)//判断当前节点是否为空

                throw new IllegalStateException();

            if (modCount != expectedModCount)//首先就是检查在开始循环时被赋予的期望修改次数跟当前HashMap的修改次数是否相同

                throw new ConcurrentModificationException();

            current = null;

            K key = p.key;

            removeNode(hash(key), key, null, false, false);

            expectedModCount = modCount;//在做出remove操作之后会被重写赋予modCount值

        }

    }

    final class KeyIterator extends HashIterator

        implements Iterator {

        public final K next() { return nextNode().key; }

    }

    final class ValueIterator extends HashIterator

        implements Iterator {

        public final V next() { return nextNode().value; }

    }

    final class EntryIterator extends HashIterator

        implements Iterator> {

        public final Map.Entry next() { return nextNode(); }

    }

那么我们什么时候用到这些迭代器呢?哇这就说的又是很大一个点

我们首先了解三个方法keySet()、values()、entrySet()

里面有一个方法叫iterator返回KeyIterator、ValueIterator、EntryIterator然后就可以遍历了。

这里我只详细说明一个

    public Set keySet() {

        Set ks = keySet;//这里的keySet是AbstractMap类里面的,因为HashMap是AbstractMap的子类

        if (ks == null) {//初始化

            ks = new KeySet();

            keySet = ks;

        }

        return ks;//最终是不是Set ks = new KeySet();KeySet的父类AbstractSet实现了Set接口,也就说明KeySet同样实现了Set接口

    }

    final class KeySet extends AbstractSet {

        public final int size()                 { return size; }//返回当前HashMap的size

        public final void clear()               { HashMap.this.clear(); }//HashMap.this调用外部类的clear()方法,因为同名所以必须显示调用

        public final Iterator iterator()     { return new KeyIterator(); }//返回key的迭代器

        public final boolean contains(Object o) { return containsKey(o); }//直接调用外部类的containsKey()方法

        public final boolean remove(Object key) {

            return removeNode(hash(key), key, null, false, true) != null;

        }

        public final Spliterator spliterator() {

            return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);

        }

        public final void forEach(Consumer super K>  action) {

            Node[] tab;

            if (action == null)

                throw new NullPointerException();

            if (size > 0 && (tab = table) != null) {

                int mc = modCount;

                for (int i = 0; i < tab.length; ++i) {

                    for (Node e = tab[i]; e != null; e = e.next)

                        action.accept(e.key);

                }

                if (modCount != mc)

                    throw new ConcurrentModificationException();

            }

        }

    }

 

 

 

    

 

 

 

 

 

 

 

 

 

 

 

 

    

    

 

你可能感兴趣的:(JDK1.8深入HashMap详解)