本文以jdk1.8中HashMap.putAll()方法爲切入點,分析其中難理解、有價值的源碼片斷(相似ctrl+鼠標左鍵查看的源碼過程)。✈觀光線路圖:putAll() --> putMapEntries() --> tableSizeFor() --> resize() --> hash() --> putVal()...html
好了,帶全設備、乾糧,準備出發~java
public void putAll(Map<? extends K, ? extends V> m) { putMapEntries(m, true); // ↓ }
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0) { if (table == null) { // pre-size float ft = ((float)s / loadFactor) + 1.0F; // ft即table此時所需capacity,「+1.0F」爲何,我的理解彌補float與int轉換、比較精度彌補,加二也何嘗不可? int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t); // ↓ } else if (s > threshold) resize(); // ↓ // 筆者疑問:原map加上m後可能須要擴容的判斷在putVal中,在此是否是更佳呢?答:由於除此以外還有其餘函數調用了putVal for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } }
// 找到大於等於cap的最小的2的冪(3的最小2的冪=4;4->4;5->8...) 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; }
這裏的「-1」能夠理解爲是爲了++保證結果值≥原值++。舉個栗子,假如cap=8(1000)。計算結果爲16(10000)。這顯然不是咱們想要的最小的2的冪。函數
關於抑或、右移的計算過程,我以size=3爲例,能夠參考便於理解:
工具
不對啊,圖裏算出來的結果等於7啊,說好的2的冪呢?別忘了這裏return最後在返回值進行了+1。源碼分析
那麼問題來了。爲何要遵循「2的冪次方」的套路呢?不只tableSizeFor如此,連一些參數初始值也暗含相似意圖(如DEFAULT_INITIAL_CAPACITY = 1 << 4)。性能
根本目的爲了提升效率,爲了使用藉助如下規律:this
取餘(%)操做中若是除數是2的冪次方,則等同於與其除數減一的與(&)操做
所以在源碼中會看到大量的「(n - 1) & hash」語句,也就是爲何要按「2的冪次方」的套路出牌了。spa
// hash table擴容至原來2倍,並將原table數據從新映射到新table中 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; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; // 清空原table if (e.next == null) // 哈希表只有一個節點,直接賦值 newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); // 紅黑樹狀況 else { // preserve order 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; }
hashMap起初使用鏈表法避免哈希衝突(相同hash值),當鏈表長度大於TREEIFY_THRESHOLD(默認爲8)時,將鏈表轉換爲紅黑樹,固然小於UNTREEIFY_THRESHOLD(默認爲6)時,又會轉回鏈表以達到性能均衡。.net
根據「e.hash & oldCap」是否爲零將原鏈表拆分紅2個鏈表,一部分仍保留在原鏈表中不須要移動,一部分移動到「原索引+oldCap」的新鏈表中。code
那麼問題來了,「e.hash & oldCap」從何而來!?
由於擴容先後hash(key)不變,oldCap與newCap皆爲「2的冪次方」,則++newCap-1的二進制最高位與oldCap最高位相同++。結合上文「index=(n-1)&hash」可知:新的index的取決於:++(n-1)二進制最高位上是0仍是1++;所以源碼做者巧妙的拉關係,以「oldCap等價於newTab的(n-1)的最高位」推出「e.hash & oldCap」!
假設咱們length從16resize到32(如下僅寫出8位,實際32位),hash(key)是不變的。下面來計算一下index:
n-1: 0000 1111-----》0001 1111【高位全0,&不影響】
hash1: 0000 0101-----》0000 0101
index1: 0000 0101-----》0000 0101【index不變】
hash2: 0001 0101-----》0001 0101
index2: 0000 0101-----》0001 0101【新index=5+16即原index+oldCap】
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
異或運算:(h = key.hashCode()) ^ (h >>> 16)原 來 的 hashCode : 1111 1111 1111 1111 0100 1100 0000 1010
移位後的hashCode: 0000 0000 0000 0000 1111 1111 1111 1111
進行異或運算 結果:1111 1111 1111 1111 1011 0011 1111 0101這樣作的好處是,能夠將hashcode高位和低位的值進行混合作異或運算,並且混合後,低位的信息中加入了高位的信息,這樣高位的信息被變相的保留了下來。摻雜的元素多了,那麼生成的hash值的隨機性會增大。
更多有意思的內容,歡迎訪問筆者小站: rebey.cn