最近回顧了下HashMap的源碼(JDK1.7),當讀到putAll方法時,發現了之前寫的TODO標記,當時由於時間匆忙沒來得及深究,現在回顧到了就再仔細思考了下
@Override public void putAll(Map<? extends K, ? extends V> m) { int numKeysToBeAdded = m.size(); if (numKeysToBeAdded == 0) return; // TODO 這里的numKeysToBeAdded是不是應該要this.size+m.size()呢? // TODO 這里確實有點問題,下面的for循環中put操作可能會導致再次resize,奇怪怎么沒人提出這個問題呢? if (numKeysToBeAdded > threshold) { // +1是為了補上被強轉為int而抹去的小數部分 int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); if (targetCapacity > MAXIMUM_CAPACITY) targetCapacity = MAXIMUM_CAPACITY; int newCapacity = table.length; while (newCapacity < targetCapacity) newCapacity <<= 1; if (newCapacity > table.length) resize(newCapacity); } for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) put(e.getKey(), e.getValue()); }
如注釋中所示 numKeysToBeAdded > threshold 就是想提前判斷Map是否需要擴容,如果需要的話則直接一步到位,從而防止循環中的put操作引起多次擴容,以次來減小 resize 方法帶來的性能開銷。
但是:我們看方法的第一行,int numKeysToBeAdded = m.size(); 如果要實現擴容一步到位的話,這里的 numKeysToBeAdded 不應該是當前Map的size加m的size之和嗎? this.size + m.size() > threshold
就擴容才能保證m中所有元素添加到當前HashMap后只觸發一次resize 。
測試代碼如下,直接debug HashMap的putAll方法,我們可以看到整個putAll是進行了兩次resize
Map map = new HashMap(4); Map m = new HashMap(8); map.put("a", "haha"); map.put("b", "haha"); map.put("c", "haha"); m.put("1", "a"); m.put("2", "a"); m.put("3", "a"); m.put("4", "a"); map.putAll(m);
JDK1.8的HashMap已經實現已經做了很大的修改,但是當我切換到1.8 debug時還是resize了兩次,為什么呢?仔細看下面的注釋(當時看源碼的時候直接把這段注釋忽略了,汗),JDK的大神們給出了如下的解釋,顯然他們也知道這個問題,但是主要考慮到m和當前的HashMap中可能存在重復的key,這樣的話就可能造成HashMap浪費了比較大的空間(畫外音:HashMap默認加載因子為0.75的設計初衷不就是采取了空間換時間的思想嚒??)
/* * Expand the map if the map if the number of mappings to be added * is greater than or equal to threshold. This is conservative; the * obvious condition is (m.size() + size) >= threshold, but this * condition could result in a map with twice the appropriate capacity, * if the keys to be added overlap with the keys already in this map. * By using the conservative calculation, we subject ourself * to at most one extra resize. */
在HashMap中 size 肯定會小於或等於 threshold ,所以putAll時當 m.size() > threshold 進行擴容,HashMap的容量增加至少1倍,則因為存在 m.size() > size 所以就算 m.size() + size > threshold(第一次擴容后) 只要再做一次擴容就可以滿足HashMap的規則了。