HashMap分析-扩容

97 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情

扩容

源码:

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; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        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;
    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)
                    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;
                        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;
}

Node<K,V>[] oldTab = table; 扩容前的数组

int oldCap = (oldTab == null) ? 0 : oldTab.length; 扩容前的数组的大小和阈值

int newCap, newThr = 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; // double threshold 扩大容量为当前容量的两倍,但不能超过 MAXIMUM_CAPACITY

else if (oldThr > 0) newCap = oldThr;
判断当前数组是否没有数据,满足条件则使用初始化的值

newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 如果初始化的值为 0,则使用默认的初始化容量

if (newThr == 0) { } 对新的容量是否等于 0进行判断

table = newTab; 开始扩容,将新的容量赋值给 table

if (oldTab != null) { 原数据不为空,将原数据复制到新 table 中

for (int j = 0; j < oldCap; ++j) { 根据容量循环数组,复制非空元素到新 table

if (e.next == null) newTab[e.hash & (newCap - 1)] = e; 如果链表只有一个,则进行直接赋值

if (loTail != null) { loTail.next = null; newTab[j] = loHead; } 将原索引放到哈希桶中

f (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; 将原索引 + oldCap 放到哈希桶中

总结

  1. 从以上源码可以看出,在扩容通过高位运算(e.hash & oldCap)来确定元素是否需要移动。
  2. HashMap 插入元素使用尾插法,扩容时使用了首尾指针保证了链表元素顺序不会倒置