zoukankan      html  css  js  c++  java
  • 阿里面试题:说说HashMap的扩容过程?

      这是一道阿里的面试题,考察你对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不会倒置

  • 相关阅读:
    Codeforces Round #669 (Div. 2) A、B题题解
    【证明】树上问题-树的直径
    Web前端开发——概述
    Codeforces Round #667 (Div. 3) A
    Codeforces Round #529 (Div. 3) 练习赛
    [Noip2012] 开车旅行 (倍增DP,难)
    国家集训队论文列表(1999-2019)
    博弈论经典模型解析(入门级)
    Problem 1342B
    SCOI2005 互不侵犯 (状态压缩入门题)
  • 原文地址:https://www.cnblogs.com/hujinshui/p/11043114.html
Copyright © 2011-2022 走看看