JAVA-HashMap源码-扩容

HashMap(1.8)


HashMap的resize方法:
代码如下:
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
//获取旧表的容量值
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //获取旧的阈值
int oldThr = threshold;
//定义新的容量、新的阈值
        int newCap, newThr = 0;
//1.判断旧表容量值是否大于0
        if (oldCap > 0) {
   //判断旧表容量值是否大于最大容量值,如果大于或等于,则阈值取:2<<30,且返回旧表,这里相当于只是调整了阈值大小,没有对旧表大小进行实际改变
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
   //初始化新容量值为旧容量值的2倍,然后与最大容量值比较,当新容量值小于最大容量值,且旧容量值大于等于默认初始化容量值,这时也对新阈值赋值为旧阈值的2倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
//2.这里的前提条件是oldCap=0,说明这是第一次创建表,将对新容量赋值为旧阈值,这里旧阈值其实就等于HashMap的初始化容量值
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
//3.当oldCap=0且oldThr=0的情况下,我们默认对新容量值和新阈值按初始化值进行赋值
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
//当进入判断条件2时,这时新阈值就会等于0
        if (newThr == 0) {
   //根据新容量值*加载因子得到新阈值
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
//修改全局变量threshold的值
        threshold = newThr;
//定义并初始化新表,指定容量
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//修改全局变量table的值
        table = newTab;
//判断旧表是否存在,如果存在,则需要将旧表中的数据复制到新表中
        if (oldTab != null) {
   //遍历旧表
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
//判断旧表中的每个位置上的元素是否存在,将该元素赋值给临时变量e
                if ((e = oldTab[j]) != null) {
   //将旧表元素置空,有利于GC
                    oldTab[j] = null;
   //因为HashMap的数据结构是数组+链表,这里需要判断该元素所在链表是否有下个元素,如果没有,则可以直接将该元素放入新表中
                    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;
   //比较hash值,这里的目的是为了处理hash冲突,使用hiHead存储哈希冲突的元素
                            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;
                        }
                    }
                }
            }
        }
        return newTab;
    }


总结:

1、扩容操作首先主要的操作是给新表的容量以及阈值正确赋值

2、创建新表后,要判断节点元素是红黑树还是链表,分别做不同的处理

3、处理链表元素时要注意哈希冲突的元素要单独提取出来存储

猜你喜欢

转载自blog.csdn.net/ignorewho/article/details/80488951