為什么要指定HashMap的容量?HashMap指定容量初始化后,底層Hash數組已經被分配內存了嗎?


為什么要指定HashMap的容量?

首先創建HashMap時,指定容量比如1024后,並不是HashMap的size不是1024,而是0,插入多少元素,size就是多少;

然后如果不指定HashMap的容量,要插入768個元素,第一次容量為16,需要持續擴容多次到1024,才能保存1024*0.75=768個元素;

所以你如果想插入1000個元素,你得需要指定容量2048,2048*0.75=1536>1000;

而HashMap擴容是非常非常消耗性能的,Java7中每次先創建2倍當前容量的新數組,然后再將老數組中的所有元素再次通過hash&(length-1)的方式散列到HashMap中;

Java8中HashMap雖然不需要重新根據hash值散列后的新數組下標,但是由於需要遍歷每個Node數組的鏈表或紅黑樹中的每個元素,根據元素key的hash值與原來老數組的容量的關系來決定放到新Node數組哪一半(2倍擴容),還是需要時間的;

具體Java8 HashMap resize源碼怎么擴容以及如何避免Java7 HashMap死循環的可簡單參考:JAVA7與JAVA8中的HASHMAP和CONCURRENTHASHMAP知識點總結

 

HashMap指定容量初始化后,底層Hash數組已經被分配內存了嗎?

Java8 HashMap指定容量構造方法源碼,會調整threshold為2的整數次冪,比如指定容量為1000,則threshold為1024:

public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
 }
 /**
     * Returns a power of two size for the given target capacity.
     */
 static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

可以根據源碼看到,在指定的HashMap初始容量后,底層的Hash數組Node<K,V>[] table此時並沒有被初始化,依舊為null

那么是什么時候被初始化的呢?答案是第一次put元素的時候。

put源碼如下:

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
......
......
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;
......
......

上面代碼的加紅字體,首先第一次調用put方法插入key-value,會調用resize方法,由於指定了HashMap的容量,那么這里會將底層的Hash數組Node<K,V>[] table初始化容量為上面所說的2的整數次冪;

resize源碼,Java7 HashMap是根據元素Hash值重新散列,Java8 HashMap卻不一樣,這里put、resize方法源碼只截取了第一次指定容量擴容相關的源碼,詳細的內容可簡單參考:JAVA7與JAVA8中的HASHMAP和CONCURRENTHASHMAP知識點總結

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM