集合是Java開發平常開發中常常會使用到的,而做爲一種典型的K-V結構的數據結構,HashMap對於Java開發者必定不陌生。面試
關於HashMap,不少人都對他有一些基本的瞭解,好比他和hashtable之間的區別、他和concurrentHashMap之間的區別等。這些都是比較常見的,關於HashMap的一些知識點和麪試題,想來你們必定了熟於心了,而且在開發中也能有效的應用上。算法
可是,做者在不少次 CodeReview 以及面試中發現,有一個比較關鍵的小細節常常被忽視,那就是HashMap建立的時候,要不要指定容量?若是要指定的話,多少是合適的?爲何?性能優化
在《HashMap中傻傻分不清楚的那些概念》中咱們曾經有過如下結論:數據結構
HashMap有擴容機制,就是當達到擴容條件時會進行擴容。HashMap的擴容條件就是當HashMap中的元素個數(size)超過臨界值(threshold)時就會自動擴容。在HashMap中,threshold = loadFactor * capacity。性能
因此,若是咱們沒有設置初始容量大小,隨着元素的不斷增長,HashMap會發生屢次擴容,而HashMap中的擴容機制決定了每次擴容都須要重建hash表,是很是影響性能的。優化
因此,首先能夠明確的是,咱們建議開發者在建立HashMap的時候指定初始化容量。而且《阿里巴巴開發手冊》中也是這麼建議的:spa
那麼,既然建議咱們集合初始化的時候,要指定初始值大小,那麼咱們建立HashMap的時候,到底指定多少合適呢?3d
有些人會天然想到,我準備塞多少個元素我就設置成多少唄。好比我準備塞7個元素,那就new HashMap(7)。code
可是,這麼作不只不對,並且以上方式建立出來的Map的容量也不是7。cdn
由於,當咱們使用HashMap(int initialCapacity)來初始化容量的時候,HashMap並不會使用咱們傳進來的initialCapacity直接做爲初識容量。
JDK會默認幫咱們計算一個相對合理的值當作初始容量。所謂合理值,實際上是找到第一個比用戶傳入的值大的2的冪。
也就是說,當咱們new HashMap(7)建立HashMap的時候,JDK會經過計算,幫咱們建立一個容量爲8的Map;當咱們new HashMap(9)建立HashMap的時候,JDK會經過計算,幫咱們建立一個容量爲16的Map。
可是,這個值看似合理,實際上並不盡然。由於HashMap在根據用戶傳入的capacity計算獲得的默認容量,並無考慮到loadFactor這個因素,只是簡單機械的計算出第一個大約這個數字的2的冪。
loadFactor是負載因子,當HashMap中的元素個數(size)超過 threshold = loadFactor * capacity時,就會進行擴容。
也就是說,若是咱們設置的默認值是7,通過JDK處理以後,HashMap的容量會被設置成8,可是,這個HashMap在元素個數達到 8*0.75 = 6的時候就會進行一次擴容,這明顯是咱們不但願見到的。
那麼,到底設置成什麼值比較合理呢?
這裏咱們能夠參考JDK8中putAll方法中的實現的,這個實如今guava(21.0版本)也被採用。
這個值的計算方法就是:
return (int) ((float) expectedSize / 0.75F + 1.0F);
複製代碼
好比咱們計劃向HashMap中放入7個元素的時候,咱們經過expectedSize / 0.75F + 1.0F計算,7/0.75 + 1 = 10 ,10通過JDK處理以後,會被設置成16,這就大大的減小了擴容的概率。
當HashMap內部維護的哈希表的容量達到75%時(默認狀況下),會觸發rehash,而rehash的過程是比較耗費時間的。因此初始化容量要設置成expectedSize/0.75 + 1的話,能夠有效的減小衝突也能夠減少偏差。(你們結合這個公式,好好理解下這句話)
因此,咱們能夠認爲,當咱們明確知道HashMap中元素的個數的時候,把默認容量設置成expectedSize / 0.75F + 1.0F 是一個在性能上相對好的選擇,可是,同時也會犧牲些內存。
這個算法在guava中有實現,開發的時候,能夠直接經過Maps類建立一個HashMap:
Map<String, String> map = Maps.newHashMapWithExpectedSize(7);
複製代碼
其代碼實現以下:
public static <K, V> HashMap<K, V> newHashMapWithExpectedSize(int expectedSize) {
return new HashMap(capacity(expectedSize));
}
static int capacity(int expectedSize) {
if (expectedSize < 3) {
CollectPreconditions.checkNonnegative(expectedSize, "expectedSize");
return expectedSize + 1;
} else {
return expectedSize < 1073741824 ? (int)((float)expectedSize / 0.75F + 1.0F) : 2147483647;
}
}
複製代碼
可是,以上的操做是一種用內存換性能的作法,真正使用的時候,要考慮到內存的影響。 可是,大多數狀況下,咱們仍是認爲內存是一種比較富裕的資源。
可是話又說回來了,有些時候,咱們到底要不要設置HashMap的初識值,這個值又設置成多少,真的有那麼大影響嗎?其實也不見得!
但是,大的性能優化,不就是一個一個的優化細節堆疊出來的嗎?
再不濟,之後你寫代碼的時候,使用Maps.newHashMapWithExpectedSize(7);的寫法,也可讓同事和老闆眼前一亮。
或者哪一天你碰到一個面試官問你一些細節的時候,你也能有個印象,或者某一天你也能夠拿這個出去面試問其餘人~!啊哈哈哈。