HashMap如何扩充?

4、HashMap如何扩充?

  • 首先看下put,通过传入hash值,key,value

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) {

    Node<K,V>[] tab; Node<K,V> p; int n, i; //新建一个tab指向hash表,

if ((tab = table) == null || (n = tab.length) == 0) //如果是空表

    n = (tab = resize()).length;    // 执行扩容方法,并把扩容后的hash表长度给n

if ((p = tab[i = (n - 1) & hash]) == null)

tab[i] = newNode(hash, key, value, null);

else {

Node<K,V> e; K k;

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) {

if ((e = p.next) == null) {

p.next = newNode(hash, key, value, null);

if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st

treeifyBin(tab, hash);

break;

}

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.equals(k))))

break;

p = e;

}

}

if (e != null) { // existing mapping for key

V oldValue = e.value;

if (!onlyIfAbsent || oldValue == null)

e.value = value;

afterNodeAccess(e);

return oldValue;

}

}

++modCount;

if (++size > threshold) //如果size大于阈值,哈希表就要进行扩充

resize();

afterNodeInsertion(evict);

return null;

}
  • HashMap的扩容方法

final Node<K,V>[] resize() {  //由此可见,扩容后将扩容后的返回去

Node<K,V>[] oldTab = table; //将hash表赋值给oldTab

int oldCap = (oldTab == null) ? 0 : oldTab.length; //如果旧表是空,容量旧容量为0,如果不是空,取旧表的长度

int oldThr = threshold; // threshold 阈值,下次需要扩容时的值,阈值=容量*加载因子

int newCap, newThr = 0; //定义新的容量,新的阈值为0

if (oldCap > 0) {    // 如果旧容量大于0

    if (oldCap >= MAXIMUM_CAPACITY) { //如果旧的容量>=最大容量: 1<<30(2的30次方)

    threshold = Integer.MAX_VALUE; //将Integer最大值给到阈值

    return oldTab; //返回旧表

    } //若旧容量< 最大容量

    else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && //如果旧容量乘以2 < 最大容量(新容量为旧容量的两倍) 并且 旧容量>= 默认初始化容量(16)

    oldCap >= DEFAULT_INITIAL_CAPACITY)  //将旧的阈值增加一倍赋值给新的阈值

    newThr = oldThr << 1; // double threshold 

} // 下面为旧容量为0,并且阈值>0, 说明之前创建了hash表,但没有添加元素,将初始化容量 = 阈值

else if (oldThr > 0) // initial capacity was placed in threshold

    newCap = oldThr; //将阈值给到新的容量

else { // 旧容量和旧的阈值都是0, 说明还没有创建hash表

    newCap = DEFAULT_INITIAL_CAPACITY;  //否则使用默认容量 和默认阈值  容量*加载因子(0.75)

    newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //

}

===================上面操作执行完成后=================================

if (newThr == 0) { // 如果新的阈值为0,就用新的容量*加载因子再算一次

    float ft = (float)newCap * loadFactor;

    newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?  //如果新的容量<最大容量 并且 新的加载因子<最大容量

    (int)ft : Integer.MAX_VALUE); // true:使用新的加载因子,false:使用Integer的最大值

}

threshold = newThr; // 更新阈值

@SuppressWarnings({"rawtypes","unchecked"}) 

Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // 创建新的链表数组,容量为原来的两倍

table = newTab; //把新的哈希表给到旧hash表

// 接下来就要遍历复制了

if (oldTab != null) {  // 如果旧的哈希表不是空

    for (int j = 0; j < oldCap; ++j) { //遍历旧的容量

        Node<K,V> e;

        if ((e = oldTab[j]) != null) { //旧表当前元素(桶)赋值给e,并且不是null

            oldTab[j] = null; //旧表当前元素赋值为空(应该是为了垃圾回收)

            if (e.next == null) //如果没有下一个元素(桶)

                newTab[e.hash & (newCap - 1)] = e; //将e添加到新的哈希表

            else if (e instanceof TreeNode) //如果是红黑树结构,就把当前表里的当前桶也变成红黑树结构

                ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);

        else { //保留哈希表中链表的顺序

                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;  //最后将扩容完的哈希表返回

}

扩容过程中几个关键的点:

  • 阈值:当达到阈值时,哈希表就进行扩充

  • 新初始化哈希表时,容量为默认容量,阈值为 容量*加载因子

  • 已有哈希表扩容时,容量、阈值均翻倍

  • 如果之前这个桶的节点类型是树,需要把新哈希表里当前桶也变成树形结构

  • 复制给新哈希表中需要重新索引(rehash),这里采用的计算方法是e.hash & (newCap - 1),等价于 e.hash % newCap

结合扩容源码可以发现扩容的确开销很大,需要迭代所有的元素,rehash、赋值,还得保留原来的数据结构。

所以在使用的时候,最好在初始化的时候就指定好 HashMap 的长度,尽量避免频繁 resize()。

参考大佬的博客: 大佬的博客

原创文章 63 获赞 48 访问量 8万+

猜你喜欢

转载自blog.csdn.net/csp_6666/article/details/100313275