HashMap1.8源码分析

HashMap数据结构

数组+链表  1.7
数组+链表+红黑树 1.8

HashMap为什么要用链表?

解决hash冲突

为什么要用红黑树?(查找接近二分查找)

方便查询(时间复杂度低)

HashMap的容量为什么要为2的次方数呢?

1.hash运算方便(在进行hash运算的时候可以通过位运算来进行,不必进行取模,提高效率)
2.在数组在扩容的时候,方便计算新的位置

hash函数

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

这里的hash函数和1.7的不同,因为1.8新增了红黑树这个结构,所以不用像1.7的那么复杂.
这种做法就是将hashCode()返回的hash值异或上该值右移16位.就是让高位参加运算,增加散列性.
这样hash的好处还有一点: 
在数组进行扩容的时候, 旧数组table[i]位置的所有值在转移到新数组的位置只会有两种情况:
1.在新数组的 newTable[i]位置
2.在新数组的 newTable[i+oldCap] 位置

为什么会这样呢?

举个例子:
有两个hashCode : e.h1 =  1010 1001    e.h2 = 1101 1001  (低4位相同,高位不同)
数组长度:  table.length = 16
该值在table的存储位置为 e.h ^ (16 -1) 
计算e.h1: 
  1 0 1 0   1 0 0 1       e.h1
& 0 0 0 0   1 1 1 1       15
= 0 0 0 0   1 0 0 1  =  9  所以放入table[9]
此时数组扩容 table.length = 2 * 16 =32
再次计算:         e.h1 ^ (32 -1) 
  1 0 1 0   1 0 0 1       e.h1
& 0 0 0 1   1 1 1 1       31
= 0 0 0 0   1 0 0 1  =  9  所以放入newTable[9]   table[i]的数放入 newTable[i]

计算e.h2:
  1 1 0 1   1 0 0 1       e.h2
& 0 0 0 0   1 1 1 1       15
= 0 0 0 0   1 0 0 1  =  9  所以放入table[9]
此时数组扩容 table.length = 2 * 16 =32
再次计算:         e.h2 ^ (32 -1) 
  1 1 0 1   1 0 0 1       e.h2
& 0 0 0 1   1 1 1 1       31
= 0 0 0 1   1 0 0 1  =  25  所以放入newTable[25]  table[i]的数放入newTable[i+oldCap]

发现再扩容之后的低四位运算不变,只是多了一位参与运算,而这一位计算的结果只有0或1, 0代表index运算结果和原先结果一样,1则代表增加了 多出来计算的那一位的值,由例子来看,在计算中新增的那一位是 16 ,所以index+新增的那一位的值即可,也可以看做是增加了原先的数组长度.

JDK 1.8 HashMap分析

属性:

//初始容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30; 
//默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//当链表的大小等于或大于8,就会变成树结构 (变树的条件之一)
static final int TREEIFY_THRESHOLD = 8; 
//当树的大小小于或等于6,就会变成链表结构
static final int UNTREEIFY_THRESHOLD = 6;
//最小树形化容量阈值为,当表的长度(数组的长度)>该值,才允许将链表树化
//否则,桶内元素过多时直接扩容,而不是树形化
//了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
static final int MIN_TREEIFY_CAPACITY = 64; (table.length>this : 是变树的第二个条件)
//主要数据结构
transient Node<K,V>[] table;

//Node节点
class Node<K,V> implement Map.Entry{
    int hash;
    K key;
    V value;
    Node<K,V> next;
}
/**TreeNode继承了LinkedHashMap.Entry ,
而Entry又继承了HashMap.Node,所以他会有HashMap的Node的属性next,
自己也有prev属性,所以在是一颗树的同时也维护了一个双向链表.
**/
TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}


构造函数

public HashMap(int initialCapacity, float loadFactor) {
    //如果给定容量小于0,则不合法,抛出异常 
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    //如果给定容量大于最大容量2<<30, 则初始化容量为最大容量 
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    //判断加载因子的合法性
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    //赋值给HashMap的属性
    this.loadFactor = loadFactor;
    //这里需要对对传入的容量大小进行转化,会转为大于或等于该值的最小2次方数 
    //例: initialCapacity = 10,  进行tableSizeFor()方法会变为 16 因为 10 < 2^4=16
    this.threshold = tableSizeFor(initialCapacity);
}
//该方法的目的就是将传入的数转为大于或等于该值的最小2次方数 
static final int tableSizeFor(int cap) {
    //先将该值-1
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

以二进制的形式进行运算
例:i=18
18 = 0001 0010
(1)  0001 0010右移一位:  0000 1001
     0001 00100000 1001 :  0001 1011
(2) 0001 1011 右移2位: 0000 0110 
     0001 10110000 01100001 1111
(3)0001 1111 右移4: 0000 0001
     0001 11110000 0000 : 0001 1111
(4)0001 1111 右移8位:0000 0000
     0001 11110000 00000001 1111
(5)0001 1111 右移16位:0000 0000
     0001 11110000 00000001 1111
此时结果为 0001 1111 也就是31 最后进行加1也就会变成 32.因为  18< (2^6=32)
右移与或运算的目的就是想让某个数字的低位都变为1,再用该结果 加1 ,即得到了想要的结果。

问题:HashMap是在你new 的是否就已经创建(为数组分配空间)好了吗?
答案:不是这样的,在分析源码的时候,HashMap的构造函数上面没有对table属性进行操作.那是在什么时候进行初始化table的呢.

不妨可以调试一下

HashMap<String,String> map = new HashMap();
map.put("1","2");

我们可以点进去put方法里面看一下put的过程:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
他首先调用了putVal()方法.
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    //创建一个tab数组,一个p引用,两个变量
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 1
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 2
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        //判断table[i]位置的key是否相等,如果相等,将节点e指向该位置
        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) {
                //如果p.next不为null , 不进入第一个if
                if ((e = p.next) == null) {
                    //如果p.next==null ,说明遍历到了最后一个节点
                    //创建一个新节点,进行尾插法
                    p.next = newNode(hash, key, value, null);
                    //需要判断bigCount是否大于7(链表长度过长需要进行树化,而树化的要求是链表的长度>8)
                    //因为bigCount是从0开始的.
                    //这里需要思考一个问题:是插入第8个元素的时候会树化,还是插入第9个元素时树化?
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                //判断p.next的key是否相等
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                //相当于 p = p.next(因为e = p.next)
                p = e;
            }
        }
        //判断e是否存在
        if (e != null) { // existing mapping for key
            //如果存在,获取旧值
            V oldValue = e.value;
            //判断是否需要更新该值.putVal()的参数onlyIfAbsent为false,所以需要更新该值
            //key相同,value值需要进行覆盖操作
            //可以在使用map.putIfAbsent("1","2");方法,表示如果key相同,不进行任何更新覆盖操作.
            if (!onlyIfAbsent || oldValue == null) 
                e.value = value;
            afterNodeAccess(e); //子类用到的方法
            //返回旧值
            return oldValue;
        }
    }
    //添加数据成功,版本号+1
    ++modCount;
    //判断新的size是否大于扩容阈值,如果大于则需要进行扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict); //子类用到的方法
    return null;
}

可以看出put的核心代码在这里.来分析一下.

  1. 在put的时候,第一件事就是去检查这个table是否为null,因为构造函数没有对table进行初始化,所以第一次调用put,table==null ,在这个时候,调用resize()方法,才进行初始化table;
  2. 数组初始化完成之后,通过hash & (table.length - 1)找出要插入的数组下标(相当于1.7的indexFor(hash,table.length)),找到位置后,判断当前位置是否为null,第一次插入肯定为null,所以直接new一个节点放到table[i]位置即可,不进入else ,modcount++(对HashMap的修改次数,也叫版本号)
  3. 进行size加一操作,然后判断size(数组存放的所有数据,包括链表存放的)是否大于阈值threshold.如果成立,则需要进行resize()扩容.(在上面初始化的时候也用到了resize(),该方法可用于初始化,同样也可用于扩容).

上面有一个问题:就是在插入第8个元素的时候会树化,还是插入第9个元素时树化?

for (int binCount = 0; ; ++binCount) {
      //如果p.next不为null , 不进入第一个if
      if ((e = p.next) == null) {
          //如果p.next==null ,说明遍历到了最后一个节点
          //创建一个新节点,进行尾插法
          p.next = newNode(hash, key, value, null);
          //需要判断bigCount是否大于7(链表长度过长需要进行树化,而树化的要求是链表的长度>8)
          //因为bigCount是从0开始的.
          //这里需要思考一个问题:是插入第8个元素的时候会树化,还是插入第9个元素时树化?
          if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
               treeifyBin(tab, hash);
               break;
          }
          //判断p.next的key是否相等
          if (e.hash == hash &&
              ((k = e.key) == key || (key != null && key.equals(k))))
               break;
          //相当于 p = p.next(因为e = p.next)
          p = e;
         
}

可以举例分析一下,因为bigCount是从0开始的,所以当我数组中有7个节点的时候,遍历完成之后++bigCount最终 = 6,接着进入if(p.next == null) ,创建新节点,插入第8个节点:p.next = newNode; 此时会去判断bigCount是否 >= 7,在这个时候,因为if()语句还没执行完,bigCount = 6 ,而现在链表的节点个数为8,因为刚刚新插入了一个节点.bigCount < 7不满足树化条件.可以看出,当插入第8个节点(元素)的时候,并没有进入if()语句,也就不会树化.
继续分析,此时链表有8个节点,最终++bigCount = 7,接着将第九个节点插入,此时判断bigCoung是否 >= 7,7 = 7 条件成立!

所以得出结论,在插入第9个节点的时候才会进行树化,也就是说当链表长度>8的时候,会进行链表树化(当然也不是绝对的,链表长度 > 8只是树化条件之一)

在putVal()方法里面有一个判断是判断bigCount >= 7(判断是否需要进行树化)
如果条件满足,来看一下方法 treeifyBin(Node<K,V>[] tab, int hash)

final void treeifyBin(Node<K,V>[] tab, int hash) {
    //定义几个变量
    int n, index; Node<K,V> e;
    //这里是关键*************
    //1.首先会判断数组是否为空,会返回false ,因为是 || 操作,所以前面为false还需要判断后面
    //2.判断数组的长度是否小于MIN_TREEIFY_CAPACITY该值. 该值为64
    //所以是否需要树化有两个条件
    // 1.当前table[i]位置链表长度是否大于8
    // 2.table的长度是否大于 64. 
    //两者需要同时满足的情况下,才会进行链表树化,否则只会进行数组的扩容resize()
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) { 
        //接下来要做的不是链表树化,而是将该table[i]下的链表组成一个双向链表
        //定义一个TreeNode头结点 hd, 和辅助节点 t1
        TreeNode<K,V> hd = null, tl = null;
        do {
            //replacementTreeNode()该方法返回一个TreeNode节点
            TreeNode<K,V> p = replacementTreeNode(e, null);
            //第一步将头结点指向Node链表的第一个节点
            if (tl == null)
                hd = p;
            else {
                //p相当于遍历Node链表的节点
                //将链表的节点插入到该双向链表TreeNode的后面
                p.prev = tl;
                tl.next = p;
            }
            //辅助t1指向p
            tl = p;
        } while ((e = e.next) != null);
        //将该双向链表放入table[i]位置,如果不为空,进行树化操作
        if ((tab[index] = hd) != null)
            //真正的链表树化操作
            hd.treeify(tab);
    }
}

从这个do{}while操作可以发现,它将原先的Node链表变成了一个双向的TreeNode链表.
也就是说后面树化操作形成的树 也是一个双向链表.

注意:链表的树化是为了方便查询,而数组的扩容,会让数据更加散列化,效果是一样的.

来看一下resize()方法到底是怎么实现的?

  • resize()初始化流程:
final Node<K,V>[] resize() {
    //首先创建一个数组指向原数组
    Node<K,V>[] oldTab = table;
    //判断原数组是否为null(这里在第一次put的时候数组还未初始化,所以为null,接下来进行初始化)
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //获取原来的扩容阈值
    int oldThr = threshold;
    //定义两个变量
    int newCap, newThr = 0;
    //判断原数组的长度是否大于 0 (扩容的时候会进入该if)
    if (oldCap > 0) {
        //判断数组旧长度是否大于最大容量
        if (oldCap >= MAXIMUM_CAPACITY) {
            //大于最大容量,则将该值赋值给扩容阈值
            threshold = Integer.MAX_VALUE;
            //直接返回旧长度,不能再扩容了
            return oldTab;
        }
        //旧长度小于最大容量
        //计算新容量newCap,odlCap<<1 也就是旧数组长度的2倍.
        //判断新数组的长度是否小于最大容量,如果成立
        //接着判断旧数组的大小是否大于默认初始容量,也就是16
        //如果成立,则设置新数组的扩容阈值为旧数组的2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    //旧数组长度<=0 ,判断旧的扩容阈值是否大于0
    else if (oldThr > 0) // initial capacity was placed in threshold
        //将新值设置为旧值
        newCap = oldThr;
    //初始化操作
    else {               // zero initial threshold signifies using defaults
        //设置新的容量为默认容量 16
        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;
    //以上是初始化工作
    //底下的都是关于扩容操作
    ..........
  • resize()扩容:
    核心代码
final Node<K,V>[] resize() {
    //首先创建一个数组指向原数组
    Node<K,V>[] oldTab = table;
    //判断原数组是否为null(这里在第一次put的时候数组还未初始化,所以为null,接下来进行初始化)
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //获取原来的扩容阈值
    int oldThr = threshold;
    //定义两个变量
    int newCap, newThr = 0;
    //判断原数组的长度是否大于 0 (扩容的时候会进入该if)
    if (oldCap > 0) {
        //判断数组旧长度是否大于最大容量
        if (oldCap >= MAXIMUM_CAPACITY) {
            //大于最大容量,则将该值赋值给扩容阈值
            threshold = Integer.MAX_VALUE;
            //直接返回旧长度,不能再扩容了
            return oldTab;
        }
        //旧长度小于最大容量
        //计算新容量newCap,odlCap<<1 也就是旧数组长度的2倍.
        //判断新数组的长度是否小于最大容量,如果成立
        //接着判断旧数组的大小是否大于默认初始容量,也就是16
        //如果成立,则设置新数组的扩容阈值为旧数组的2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }    
    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"})
    //创建新的数组,大小为2*OldCap
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    //原数组指向新数组
    table = newTab;
    //进行数据的转移操作
    if (oldTab != null) {
        //遍历原数组
        for (int j = 0; j < oldCap; ++j) {
            //定义一个辅助节点
            Node<K,V> e;
            //遍历每个table[i]位置的数据
            if ((e = oldTab[j]) != null) {
                //将老数组[j]位置置为null, help GC
                oldTab[j] = null;
                //如果e.next == 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);
                //到了这个位置就只有一种可能,是链表
                //这个时候需要注意hash & table.length-1的好处了,在上面hash函数说到,
                //原先table[i]位置上的节点再扩容之后只会有两个位置
                //1. newTable[i]
                //2. newTable[i+OldCap]
                else { // preserve order
                    //与1.7不同的是,因为旧节点在新数组的下标只能有两个
                    //就用两个链表将newTable[i]和 newTable[i+OldCap]位置的数据链起来
                    //loHead指向将插入newTable[i]的节点
                    //hiHead指向将插入newTable[i+OldCap]的节点
                    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);
                    //如果链表不为空,则直接将链表头部放入新数组newTable[i]位置上
                    //链表为空则旧数组的节点没有要放入newTable[i]位置
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    //同上操作,两者只能有一个为空(要么全部放入newTable[i]位置上,
                    //        要么全部放入newTable[i+OldCap]位置上)
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    //返回新数组
    return newTab;
}

树的数据转移过程:

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

            if (loHead != null) {
            	//1.loHead != null -> 节点长度是否小于等于6(因为树的节点个数小于6要将树转为链表).
                if (lc <= UNTREEIFY_THRESHOLD)
                	//若小于等于6,则将loHead变为一个**Node**类型的链表(**loHead定义为TreeNode类型**).
                    tab[index] = loHead.untreeify(map);
                else {
                	//将table[i] = loHead; 接着判断hiHead是否为空,若不为空则将loHead变为TreeNode类型的树.
                    tab[index] = loHead;
                    //(注意:hiHead如果为空,则loHead就相当于原先的那棵完整的树,不需要在转化为树.
					//而hiHead不为null,则loHead为原树的一部分数据,而loHead本身为链表的头,所以要转为一棵树). 
                    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);
                }
            }
        }
//将TreeNode类型转为Node类型
final Node<K,V> untreeify(HashMap<K,V> map) {
            Node<K,V> hd = null, tl = null;
            for (Node<K,V> q = this; q != null; q = q.next) {
            	//返回 q的Node节点类型
                Node<K,V> p = map.replacementNode(q, null);
                if (tl == null)
                    hd = p;
                else
                    tl.next = p;
                tl = p;
            }
            return hd;
        }

扩容过程:
1.获取原先table数组长度oldCap,扩容阈值oldTr
2.二倍扩容newCap = oldCap << 1,newTr = oldTr << 1
3.创建一个新数组newTab = new Node[newCap],将原先数组指向新数组
4.进行数据转移 (三种情况)

  • 4.1.数组table[i]位置只有一个元素:直接将数据转移到新数组的位置(newIndex = e.hash & (newCap-1))
  • 4.2.table[i]存放的是一棵树:同链表转移一样,上面说过,树其实也是一个双向链表,因为旧数组table[i]位置上的元素转移到新数组时存放的位置只有两种:一种是newTab[i],另一种是newTab[ i + oliCap].所以会发现定义了四个TreeNode节点:loHead,loTail和hiHead,hiTail,分别代表即将存在新数组两个位置的节点头和尾.
    • 4.2.1 :然后遍历链表,计算节点在新数组的下标值(e.hash & oldCap),根据根据下标值确定节点添加在loHead还是 hiHead后面.对loHead和hiHead的节点个数进行记录.最后loHead链表连接的节点是要插入newTab[i]位置的,而hiHead链表连接的节点是要插入newTab[i+oldCap]位置的.
    • 4.2.2 :这个时候链表loHead和hiHead可能会有一个为空,因为有可能节点在新数组的位置经过计算后都在一个位置,所以要进行判断:1. loHead != null -> 节点长度是否小于等于6(因为树的节点个数小于6要将树转为链表).若小于等于6,则将loHead变为一个Node类型的链表(loHead定义为TreeNode类型).将table[i] = loHead; 接着判断hiHead是否为空,若不为空则将loHead变为TreeNode类型的树.(注意:hiHead如果为空,则loHead就相当于原先的那棵完整的树,不需要在转化为树.而hiHead不为null,则loHead为原树的一部分数据,而loHead本身为链表的头,所以要转为一棵树). 2. hiHead != null 同上.
  • 4.3.table[i]存放的是一个链表,其实和树转移操作类似,定义四个Node类型的节点:lohead,loTail,hiHead,hiTail,计算e.hash & OldCap,若等于0,则e将会存放在新数组的newTab[i]位置,用loHead将节点连接起来.等于1,则e将会存放在新数组 newTab[i+oldCap]位置,用hiHead将节点连接起来.最后将loHead,和hiHead放入新数组对应的位置即可.

删除方法

remove()方法

final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        //判读数组是否为空,和table[i]位置是否为空
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            //node指向待删除节点
            Node<K,V> node = null, e; K k; V v;
            //判断table[i]位置的节点是否是要删除的
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                //如果是,将临时节点node指向 table[i]位置节点 p
                node = p;
            //不止一个元素
            else if ((e = p.next) != null) {
            	//该位置存放的是一棵树
                if (p instanceof TreeNode)
                	//从树里面查找
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                	//该位置存放的是一个链表
                    do {
                    	//遍历链表
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            //判断是否找到了待删除节点
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                //如果待删除节点在树里
                if (node instanceof TreeNode)
                	//从树里面移除该节点
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                	//如果待删除节点是第一个元素,将p.next放入table[i]
                    tab[index] = node.next;
                else
                	//存在链表里面,将待删除节点node前驱的next->node.next
                    p.next = node.next;
                //版本号加1(修改次数)
                ++modCount;
                //数据个数-1
                --size;
                afterNodeRemoval(node);
                //返回删除的节点
                return node;
            }
        }
        //没有找到或者,找到了但不进行任何操作,返回null,和putAbsent一个道理
        return null;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值