為什么要指定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知識點總結