阿里面试题:说说HashMap的扩容过程?

被刻印的时光 ゝ 提交于 2020-12-23 18:44:44

  这是一道阿里的面试题,考察你对HashMap源码的了解情况,废话不多说,咱们就直接上源码吧!

jdk 1.7 源码

void resize(int newCapacity) {
        Entry[] oldTable = table;//保存旧数组
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {//判断当前数组大小是否达到最大值
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];//创建一个新数组
        boolean oldAltHashing = useAltHashing;
        useAltHashing |= sun.misc.VM.isBooted() &&
                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean rehash = oldAltHashing ^ useAltHashing;//是否需要重新计算hash值
        transfer(newTable, rehash);//将oldTable的元素迁移到newTable
        table = newTable;//将新数组重新赋值
              //重新计算阈值
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {//遍历oldTable迁移元素到newTable
            while(null != e) {//①处会导致闭环,从而导致e永远不为空,然后死循环,内存直接爆了
                Entry<K,V> next = e.next;
                if (rehash) {//是否需要重新计算hash值
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];//
                newTable[i] = e;//
                e = next;//
            }
        }
}

jdk 1.8 源码(比较长,慢慢品哈)

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;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; //扩容两倍的阈值
        }
        else if (oldThr > 0) // 初始化新的数组大小
            newCap = oldThr;
        else {//上面条件都不满足,则使用默认值
            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"})
              //创建一个newCap大小的数组Node
        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;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;//释放空间
                    if (e.next == null)
                      //如果旧数组中e后面没有元素,则直接计算新数组的位置存放
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)//如果是红黑树则单独处理
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { //链表结构逻辑,解决hash冲突
                        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) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //原索引+oldCap放入数组中
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;//jdk1.8优化的点
                        }
                    }
                }
            }
        }
        return newTab;
    }

总结

  jdk1.7扩容是重新计算hash;jdk1.8是要看看原来的hash值新增的那个bit是1还是0好了,如果是0则索引没变,如果是1则索引变成"原索引+oldCap".这是jdk1.8的亮点,设计的确实非常的巧妙,即省去了重新计算hash值得时间,又均匀的把之前的冲突的节点分散到新的数组bucket上

   jdk1.7在rehash的时候,旧链表迁移到新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是jdk1.8不会倒置

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!