做爲重要的經常使用集合,HashMap主要是提供鍵值對的存取,經過key值能夠快速找到對應的value值。Hash表是經過提早設定好的規則計算一個元素的hash值來找到他在數組中的存儲位置進行快速定位,假設有一個大小爲10的數組,能夠設定簡單的計算規則爲元素轉爲int後mod 10,由此元素的hash值必定會落在大小爲10的數組內。因爲不一樣元素可能會計算出相同的hash值,如例子中1和11都應該在下標爲1的位置,這就是hash值的衝突。爲了解決這個問題有幾種經常使用的策略:html
由此引入一個hash表的屬性——負載因子,負載因子=存儲的元素個數/數組大小。很顯然,鏈表法因爲衝突位置鏈無限延長的特色,若不加以限制負載因子能夠超過1,負載因子越大表明表中的數據越密集。java
HashMap的key和value值均可覺得null,get操做時若找不到對應的key值會返回null,具體見下方的例子:node
1 public static void main(String args[]){ 2 Map<String, String> map = new HashMap<>(); 3 System.out.println(map.put(null, "123"));//null 4 System.out.println(map.put("456", null));//null 5 System.out.println(map.get("123"));//null 6 System.out.println(map.get(null));//123 7 System.out.println(map.get("456"));//null 8 System.out.println(map.put(null, "345"));//123 9 System.out.println(map.get(null));//345 10 }
由於篇幅加難度的緣由TreeNode部分的分析見Java HashMap類源碼解析(續)-TreeNode數組
首先大體翻譯下note的主要內容:一般是桶式hash表(鏈表解決衝突),可是桶過大達到TREEIFY_THRESHOLD值的時候會轉爲樹狀TreeNode使得密度太高時的操做能夠變得更快。通常對象在樹中是按hashcode排序,可是對於實現了Comparable<C>的對象是經過comapreTo來排序。因爲TreeNode的大小接近普通node的兩倍,當桶變小時會轉回線性鏈表。TreeNode是JDK8引入的紅黑樹結構,樹根一般是hash映射的第一個結點,除了Iterator.remove以外。鏈表在樹化或是分裂時保證結點的遍歷順序是一致的。緩存
1 static class Node<K,V> implements Map.Entry<K,V> { 2 final int hash; 3 final K key; 4 V value; 5 Node<K,V> next; 6 7 Node(int hash, K key, V value, Node<K,V> next) { 8 this.hash = hash; 9 this.key = key; 10 this.value = value; 11 this.next = next; 12 } 13 14 public final K getKey() { return key; } 15 public final V getValue() { return value; } 16 public final String toString() { return key + "=" + value; } 17 18 public final int hashCode() { 19 //key和value的hashCode亦或 20 return Objects.hashCode(key) ^ Objects.hashCode(value); 21 } 22 23 public final V setValue(V newValue) { 24 V oldValue = value; 25 value = newValue; 26 return oldValue; 27 } 28 29 public final boolean equals(Object o) { 30 if (o == this) 31 return true; 32 if (o instanceof Map.Entry) { 33 Map.Entry<?,?> e = (Map.Entry<?,?>)o; 34 //key == e.getKey()或者key.equals(e.getKey()) 35 if (Objects.equals(key, e.getKey()) && 36 Objects.equals(value, e.getValue())) 37 return true; 38 } 39 return false; 40 } 41 }
而後咱們來看下Node的結構。通常的箱式結點實現了Map.Entry<K,V>接口,這是一個key-value鍵值對,內部有4個屬性hash值、K、V以及指向下個結點的引用。hashCode方法是對key和value的hash值求異或,也重寫了equals和toString方法。安全
1 static final int hash(Object key) { 2 int h; 3 //key的hashCode無符號右移16位的值與本身進行異或 4 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 5 }
對於HashMap自身的hash方法,這樣作的目的是避免hash值高位由於表大小而永遠不會被用於hash值的計算,使得分佈能夠更加均勻。app
1 static final int tableSizeFor(int cap) { 2 int n = cap - 1;//cap=0001 1000 0001 1111(6175) n = 0001 1000 0001 1110 3 n |= n >>> 1;//n = 0001 1100 0001 1111 4 n |= n >>> 2;//n = 0001 1111 0001 1111 5 n |= n >>> 4;//n = 0001 1111 1111 1111 6 n |= n >>> 8;//n = 0001 1111 1111 1111 7 n |= n >>> 16;//n = 0001 1111 1111 1111(8191) 8 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; 9 }
根據cap返回剛好大於等於該值的2的指數大小。以cap = 6175進行演示,可得n最終爲8191即2^13-1,因此返回值爲2^13ide
HashMap內部屬性以下:函數
//hash表的底層數組,大小永遠是2的指數 transient Node<K,V>[] table; //含有鍵值對的set高速緩存 transient Set<Map.Entry<K,V>> entrySet; //鍵值對的數量 transient int size; //HashMap被結構性修改的次數,包括改變鍵值對個數的操做和rehash等改變內部結構的操做,用於迭代器在線程不安全時快速拋錯 transient int modCount; //達到某個大小後就要改變數組大小,等於capacity * load factor int threshold; //負載因子 final float loadFactor;
構造函數:this
1 //參數缺省值爲16和0.75 2 public HashMap(int initialCapacity, float loadFactor) { 3 if (initialCapacity < 0) 4 throw new IllegalArgumentException("Illegal initial capacity: " + 5 initialCapacity); 6 //大小最大不能超過1<<30 7 if (initialCapacity > MAXIMUM_CAPACITY) 8 initialCapacity = MAXIMUM_CAPACITY; 9 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 10 throw new IllegalArgumentException("Illegal load factor: " + 11 loadFactor); 12 this.loadFactor = loadFactor; 13 //計算剛好大於等於initialCapacity的2的指數做爲容量大小 14 this.threshold = tableSizeFor(initialCapacity); 15 } 16 public HashMap(Map<? extends K, ? extends V> m) { 17 this.loadFactor = DEFAULT_LOAD_FACTOR; 18 putMapEntries(m, false); 19 }
這裏經過一個已有Map來構造時用到了putMapEntries這個方法,先來看下這個方法
1 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { 2 int s = m.size(); 3 if (s > 0) { 4 if (table == null) { //當前沒有元素 5 float ft = ((float)s / loadFactor) + 1.0F;//計算出m的容量 6 int t = ((ft < (float)MAXIMUM_CAPACITY) ? 7 (int)ft : MAXIMUM_CAPACITY); 8 if (t > threshold) 9 threshold = tableSizeFor(t); 10 } 11 else if (s > threshold) 12 resize();//若m的元素個數超過了threhold則須要擴展表 13 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { 14 K key = e.getKey(); 15 V value = e.getValue(); 16 putVal(hash(key), key, value, false, evict);//將鍵值對插入到表中 17 } 18 } 19 }
能夠看到其中調用了用於擴展表的resize和插入鍵值對的putVal。先看resize方法,這個方法在表大小超過threhold時就會被調用,做用就是擴展數組大小,並將元素複製到數組中,同時對於衝突鏈表不須要從新計算hash值而是會根據他們的hash值決定要不要複製到數組的高位去
1 final Node<K,V>[] resize() { 2 Node<K,V>[] oldTab = table; 3 int oldCap = (oldTab == null) ? 0 : oldTab.length;//當前表中元素個數 4 int oldThr = threshold; 5 int newCap, newThr = 0; 6 if (oldCap > 0) { 7 if (oldCap >= MAXIMUM_CAPACITY) { 8 threshold = Integer.MAX_VALUE; 9 return oldTab; 10 } 11 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 12 oldCap >= DEFAULT_INITIAL_CAPACITY) 13 newThr = oldThr << 1; // 當前表中元素個數大於等於16且小於上限的一半時,threshold加倍 14 } 15 else if (oldThr > 0) // 這個條件成立時說明構造時給了capacity參數,由此計算出了threhold 16 newCap = oldThr; 17 else { //沒有任何參數的初始化直接使用默認值 18 newCap = DEFAULT_INITIAL_CAPACITY; 19 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 20 } 21 if (newThr == 0) {//當前表中沒有元素的狀況 22 float ft = (float)newCap * loadFactor; 23 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 24 (int)ft : Integer.MAX_VALUE); 25 } 26 threshold = newThr; 27 @SuppressWarnings({"rawtypes","unchecked"}) 28 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 29 table = newTab; 30 if (oldTab != null) { 31 for (int j = 0; j < oldCap; ++j) { 32 Node<K,V> e; 33 if ((e = oldTab[j]) != null) { 34 oldTab[j] = null; 35 if (e.next == null) 36 //e沒有後續鏈表結點時,由於newCap是oldCap的2倍,至關於掩碼多了一位,本來hash值的這個多出來的有效位是0或1會決定它在新數組中下標是否變化 37 newTab[e.hash & (newCap - 1)] = e; 38 else if (e instanceof TreeNode) 39 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 40 else { // 存在非樹的鏈表時,保持前後順序不變 41 Node<K,V> loHead = null, loTail = null;//低位鏈表 42 Node<K,V> hiHead = null, hiTail = null;//高位鏈表 43 Node<K,V> next; 44 do { 45 next = e.next; 46 //這裏的運算至關於直接檢查hash新增的高位是0仍是1,由於oldCap是2的指數因此只有最高位是1其他都是0,舊hash的高位爲1時要進行移動 47 if ((e.hash & oldCap) == 0) { 48 if (loTail == null) 49 loHead = e; 50 else 51 loTail.next = e; 52 loTail = e; 53 } 54 else { 55 if (hiTail == null) 56 hiHead = e; 57 else 58 hiTail.next = e; 59 hiTail = e; 60 } 61 } while ((e = next) != null); 62 if (loTail != null) { 63 loTail.next = null; 64 newTab[j] = loHead;//低位鏈表直接複製到本來所在的位置 65 } 66 if (hiTail != null) { 67 hiTail.next = null; 68 newTab[j + oldCap] = hiHead;//高位鏈表的移動規則是本來的下標+oldCap 69 } 70 } 71 } 72 } 73 } 74 return newTab; 75 }
putVal這個方法是把值存入表中,在多個put類方法中被調用
1 /** 2 * Implements Map.put and related methods 3 * 4 * @param hash hash for key 5 * @param key the key 6 * @param value the value to put 7 * @param onlyIfAbsent if true, don't change existing value爲true表示不改變已有值 8 * @param evict if false, the table is in creation mode.爲false表示是新建表 9 * @return previous value, or null if none 10 */ 11 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 12 boolean evict) { 13 Node<K,V>[] tab; Node<K,V> p; int n, i; 14 if ((tab = table) == null || (n = tab.length) == 0) 15 n = (tab = resize()).length;//當前表的table數組爲空時需進行擴展 16 if ((p = tab[i = (n - 1) & hash]) == null)//hash值截斷到n-1對應的位數進行定位 17 tab[i] = newNode(hash, key, value, null);//若該下標位置爲空,則直接放入數組 18 else { 19 Node<K,V> e; K k; 20 if (p.hash == hash && 21 ((k = p.key) == key || (key != null && key.equals(k)))) 22 e = p;//檢查表上的根結點的hash與key值是否與新增的結點相等,若相等則將修改根結點的value 23 else if (p instanceof TreeNode) 24 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//已是樹調用樹的遍歷方法 25 else { 26 for (int binCount = 0; ; ++binCount) { 27 if ((e = p.next) == null) { 28 p.next = newNode(hash, key, value, null);//若在箱式鏈表中沒有找到key相等的結點,則新建結點插入到鏈表末尾 29 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 30 treeifyBin(tab, hash);//若增長該結點後,鏈表上的結點數超過了TREEIFY_THRESHOLD則轉爲樹,該判斷僅在遍歷到鏈表末尾時執行 31 break; 32 } 33 if (e.hash == hash && 34 ((k = e.key) == key || (key != null && key.equals(k))))//找到了key和hash值相等的結點 35 break; 36 p = e; 37 } 38 } 39 if (e != null) { // 找到了相同的key則修改value值並返回舊的value 40 V oldValue = e.value; 41 if (!onlyIfAbsent || oldValue == null) 42 e.value = value; 43 afterNodeAccess(e); 44 return oldValue; 45 } 46 } 47 ++modCount;//新增結點時增長modCount 48 if (++size > threshold) 49 resize();//大小超過threshold時要擴容 50 afterNodeInsertion(evict);//這個方法是用於繼承了HashMap的LinkedHashMap,用來移除最先放入的結點,保持插入的順序,爲false時表明是新建表不須要進行這個過程 51 return null; 52 }
treeifyBin將指定hash值對應的位置上的鏈表替換爲樹,除非整個表的大小過小時調用resize,(n - 1) & hash等效於hash mod n,只保留hash除以n的餘數做爲index的值
1 final void treeifyBin(Node<K,V>[] tab, int hash) { 2 int n, index; Node<K,V> e; 3 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) 4 resize();//表長度小於MIN_TREEIFY_CAPACITY調用resize 5 else if ((e = tab[index = (n - 1) & hash]) != null) {//該hash值對應的index位置上有元素 6 TreeNode<K,V> hd = null, tl = null; 7 do { 8 TreeNode<K,V> p = replacementTreeNode(e, null);//將鏈表轉換爲樹 9 if (tl == null) 10 hd = p; 11 else { 12 p.prev = tl; 13 tl.next = p; 14 } 15 tl = p; 16 } while ((e = e.next) != null); 17 if ((tab[index] = hd) != null) 18 hd.treeify(tab);//樹放入index的位置 19 } 20 }
對於移除操做會調用removeNode,這個方法在多個移除方法中被使用。若移除指定key值成功的結點會返回value值,不然返回null
1 public V remove(Object key) { 2 Node<K,V> e; 3 return (e = removeNode(hash(key), key, null, false, true)) == null ? 4 null : e.value; 5 } 6 /** 7 * 用於移除操做 8 * 9 * @param hash hash for key 10 * @param key the key 11 * @param value 僅matchValue爲true時須要考慮,其餘時間不起效 12 * @param matchValue 爲true時只移除value相等的 13 * @param movable 爲false時不移動其餘結點 14 * @return the node, or null if none 15 */ 16 final Node<K,V> removeNode(int hash, Object key, Object value, 17 boolean matchValue, boolean movable) { 18 Node<K,V>[] tab; Node<K,V> p; int n, index; 19 if ((tab = table) != null && (n = tab.length) > 0 && 20 (p = tab[index = (n - 1) & hash]) != null) {//表不爲空且hash值對應的index位置存在元素 21 Node<K,V> node = null, e; K k; V v; 22 if (p.hash == hash && 23 ((k = p.key) == key || (key != null && key.equals(k))))//根結點的key值相等 24 node = p; 25 else if ((e = p.next) != null) {//根結點key值不相等,存在後續結點 26 if (p instanceof TreeNode) 27 node = ((TreeNode<K,V>)p).getTreeNode(hash, key);//調用樹的遍歷方法尋找結點 28 else { 29 do { 30 if (e.hash == hash &&//爲箱式鏈表時遍歷鏈表尋找key相等的點 31 ((k = e.key) == key || 32 (key != null && key.equals(k)))) { 33 node = e; 34 break; 35 } 36 p = e; 37 } while ((e = e.next) != null); 38 } 39 } 40 if (node != null && (!matchValue || (v = node.value) == value || 41 (value != null && value.equals(v)))) {//matchValue爲true還須要驗證value是否相等,不然忽略 42 if (node instanceof TreeNode) 43 ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);//移除樹結點 44 else if (node == p) 45 tab[index] = node.next;//爲箱式鏈表根結點時,將第二個結點放到數組上 46 else 47 p.next = node.next;//爲箱式鏈表非結點時,修改上下結點間的指針 48 ++modCount;//增長modCount 49 --size; 50 afterNodeRemoval(node);//預留給LinkedHashMap的方法 51 return node; 52 } 53 } 54 return null; 55 }
clear方法不難理解,將表內全部元素設爲null,size變爲0,增長modCount
1 public void clear() { 2 Node<K,V>[] tab; 3 modCount++; 4 if ((tab = table) != null && size > 0) { 5 size = 0; 6 for (int i = 0; i < tab.length; ++i) 7 tab[i] = null; 8 } 9 }
尋找表內有無相等的value,遍歷整個鏈表找到則返回true
1 public boolean containsValue(Object value) { 2 Node<K,V>[] tab; V v; 3 if ((tab = table) != null && size > 0) { 4 for (int i = 0; i < tab.length; ++i) {//遍歷hash表 5 for (Node<K,V> e = tab[i]; e != null; e = e.next) {//遍歷鏈表 6 if ((v = e.value) == value || 7 (value != null && value.equals(v))) 8 return true; 9 } 10 } 11 } 12 return false; 13 }
key是一個set集合,value是一個collection集合,調用keySet()和values()返回的集合是對HashMap中key和value的直接引用,因此操做會直接反應在HashMap上
1 public Set<K> keySet() { 2 Set<K> ks = keySet; 3 if (ks == null) { 4 ks = new KeySet(); 5 keySet = ks; 6 } 7 return ks; 8 } 9 10 final class KeySet extends AbstractSet<K> { 11 public final int size() { return size; } 12 public final void clear() { HashMap.this.clear(); }//調用的是HashMap.clear(),因此整個表會被清空 13 public final Iterator<K> iterator() { return new KeyIterator(); } 14 public final boolean contains(Object o) { return containsKey(o); } 15 public final boolean remove(Object key) { 16 return removeNode(hash(key), key, null, false, true) != null; 17 } 18 public final Spliterator<K> spliterator() { 19 return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); 20 } 21 public final void forEach(Consumer<? super K> action) { 22 Node<K,V>[] tab; 23 if (action == null) 24 throw new NullPointerException(); 25 if (size > 0 && (tab = table) != null) { 26 int mc = modCount; 27 for (int i = 0; i < tab.length; ++i) { 28 for (Node<K,V> e = tab[i]; e != null; e = e.next) 29 action.accept(e.key); 30 } 31 if (modCount != mc)//遍歷迭代器要求不能被其餘線程修改表內元素個數而引發modCount變化 32 throw new ConcurrentModificationException(); 33 } 34 } 35 }
根據上面對putVal的分析,該方法不會改變已有的key值,返回值爲舊值或null
1 public V putIfAbsent(K key, V value) { 2 return putVal(hash(key), key, value, true, true); 3 }
而後來看一下兩個replace方法,區別在於返回值和是否檢查value值
1 @Override 2 public boolean replace(K key, V oldValue, V newValue) { 3 Node<K,V> e; V v; 4 if ((e = getNode(hash(key), key)) != null && 5 ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {//key和value要同時符合條件 6 e.value = newValue; 7 afterNodeAccess(e);//也是用於LinkedHashMap保持結點插入順序用的 8 return true; 9 } 10 return false; 11 } 12 13 @Override 14 public V replace(K key, V value) { 15 Node<K,V> e; 16 if ((e = getNode(hash(key), key)) != null) {//僅key符合條件 17 V oldValue = e.value; 18 e.value = value; 19 afterNodeAccess(e); 20 return oldValue; 21 } 22 return null; 23 }
computeIfAbsent這個方法的做用是若key值在map中已有非null的value值,則直接返回舊value值;若value值爲null則根據mappingFunction計算出新的value值並修改map中存在的鍵值對,返回新value值;若不存在key值則新增一個鍵值對插入到key的hash值對應的table數組位置鏈表的頭部,並返回新的value值。注意,putVal方法插入的結點是在鏈表尾部,而該方法是在鏈表頭部。
1 public V computeIfAbsent(K key, 2 Function<? super K, ? extends V> mappingFunction) { 3 if (mappingFunction == null) 4 throw new NullPointerException(); 5 int hash = hash(key); 6 Node<K,V>[] tab; Node<K,V> first; int n, i; 7 int binCount = 0; 8 TreeNode<K,V> t = null; 9 Node<K,V> old = null; 10 if (size > threshold || (tab = table) == null || 11 (n = tab.length) == 0) 12 n = (tab = resize()).length;//table空間不足時擴展數組 13 if ((first = tab[i = (n - 1) & hash]) != null) {//hash值對應的下標在table內不爲空 14 if (first instanceof TreeNode) 15 old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key); 16 else { 17 Node<K,V> e = first; K k; 18 do { 19 if (e.hash == hash && 20 ((k = e.key) == key || (key != null && key.equals(k)))) {//對箱式鏈表搜索key相等的結點 21 old = e; 22 break; 23 } 24 ++binCount; 25 } while ((e = e.next) != null); 26 } 27 V oldValue; 28 if (old != null && (oldValue = old.value) != null) {//找到了key相等的結點且value不爲null 29 afterNodeAccess(old);//LinkedHashMap方法 30 return oldValue;//返回舊value值 31 } 32 } 33 V v = mappingFunction.apply(key);//根據function計算出新的v值 34 if (v == null) { 35 return null;//新的v值爲null則直接返回 36 } else if (old != null) {//找到了key相等的結點且value爲null,賦予新v值後返回新v值 37 old.value = v; 38 afterNodeAccess(old); 39 return v; 40 } 41 else if (t != null) 42 t.putTreeVal(this, tab, hash, key, v);//樹結點處理 43 else { 44 tab[i] = newNode(hash, key, v, first);//沒有找到key相等的結點,新建一個結點而且插入到鏈表頭部 45 if (binCount >= TREEIFY_THRESHOLD - 1) 46 treeifyBin(tab, hash);//若新增結點後鏈表長度達到了TREEIFY_THRESHOLD則轉爲樹 47 } 48 ++modCount;//該部分僅新增結點時執行 49 ++size; 50 afterNodeInsertion(true); 51 return v; 52 }
而後是兩個相近的方法:computeIfPresent存在key相等且value不爲null的結點,計算新的value值,新value不爲null則覆蓋,新value爲null則移除本來的結點。compute方法結合了前二者,存在key相等的結點時不考慮舊value值,新value爲null則移除,不爲null則覆蓋value值;不存在key相等的結點時,新value值不爲null則新增結點,對箱式鏈表插入到鏈表頭部,插入後要檢車是否須要轉爲樹。
1 public V computeIfPresent(K key, 2 BiFunction<? super K, ? super V, ? extends V> remappingFunction) { 3 if (remappingFunction == null) 4 throw new NullPointerException(); 5 Node<K,V> e; V oldValue; 6 int hash = hash(key); 7 if ((e = getNode(hash, key)) != null && 8 (oldValue = e.value) != null) {//存在key相等且value不爲null的結點 9 V v = remappingFunction.apply(key, oldValue); 10 if (v != null) { 11 e.value = v;//新value值不爲null則修改value值 12 afterNodeAccess(e); 13 return v; 14 } 15 else 16 removeNode(hash, key, null, false, true);//新value值爲null則移除這個結點 17 } 18 return null; 19 } 20 21 @Override 22 public V compute(K key, 23 BiFunction<? super K, ? super V, ? extends V> remappingFunction) { 24 if (remappingFunction == null) 25 throw new NullPointerException(); 26 int hash = hash(key); 27 Node<K,V>[] tab; Node<K,V> first; int n, i; 28 int binCount = 0; 29 TreeNode<K,V> t = null; 30 Node<K,V> old = null; 31 if (size > threshold || (tab = table) == null || 32 (n = tab.length) == 0) 33 n = (tab = resize()).length;//table空間不足時調用resize 34 if ((first = tab[i = (n - 1) & hash]) != null) { 35 if (first instanceof TreeNode) 36 old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);//樹中尋找key相等的結點 37 else { 38 Node<K,V> e = first; K k; 39 do { 40 if (e.hash == hash && 41 ((k = e.key) == key || (key != null && key.equals(k)))) { 42 old = e;//找到了key相等的結點 43 break; 44 } 45 ++binCount; 46 } while ((e = e.next) != null); 47 } 48 } 49 V oldValue = (old == null) ? null : old.value; 50 V v = remappingFunction.apply(key, oldValue); 51 if (old != null) { 52 if (v != null) { 53 old.value = v;//找到了key值相等的結點且新value不爲null則舊結點的value設爲新值 54 afterNodeAccess(old); 55 } 56 else 57 removeNode(hash, key, null, false, true);//找到了key值相等的結點且新value爲null則移除舊結點 58 } 59 else if (v != null) {//沒有找到key值相等的結點且新value值不爲null 60 if (t != null) 61 t.putTreeVal(this, tab, hash, key, v);//樹中插入新結點 62 else { 63 tab[i] = newNode(hash, key, v, first);//新建結點插入到鏈表頭部 64 if (binCount >= TREEIFY_THRESHOLD - 1) 65 treeifyBin(tab, hash);//新增後鏈表長度達到TREEIFY_THRESHOLD則轉爲樹 66 } 67 ++modCount;//僅新增結點時執行 68 ++size; 69 afterNodeInsertion(true); 70 } 71 return v; 72 }
merge這個方法和前面差很少,也是先尋找key值相同的結點,若存在則看該結點value是否爲null,不爲null根據function和參數中的value以及結點本來的value計算出新的value值,不然直接賦予參數中的value值。若沒有找到結點,則按照參數中key和value值新建一個結點插入到樹中或者箱式鏈表的頭部。
1 public V merge(K key, V value, 2 BiFunction<? super V, ? super V, ? extends V> remappingFunction) { 3 if (value == null) 4 throw new NullPointerException(); 5 if (remappingFunction == null) 6 throw new NullPointerException(); 7 int hash = hash(key); 8 Node<K,V>[] tab; Node<K,V> first; int n, i; 9 int binCount = 0; 10 TreeNode<K,V> t = null; 11 Node<K,V> old = null; 12 if (size > threshold || (tab = table) == null || 13 (n = tab.length) == 0) 14 n = (tab = resize()).length;//表空間不足時調用resize 15 if ((first = tab[i = (n - 1) & hash]) != null) { 16 if (first instanceof TreeNode) 17 old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);//尋找樹中key值相等的結點 18 else { 19 Node<K,V> e = first; K k; 20 do { 21 if (e.hash == hash && 22 ((k = e.key) == key || (key != null && key.equals(k)))) { 23 old = e;//尋找箱式鏈表中key值相等的結點 24 break; 25 } 26 ++binCount; 27 } while ((e = e.next) != null); 28 } 29 } 30 if (old != null) {//尋找到key值相等的結點 31 V v; 32 if (old.value != null)//舊值不爲null 33 v = remappingFunction.apply(old.value, value);//根據remappingFunction和舊value和參數中的value計算出新的value值 34 else 35 v = value;//舊值爲null則新value=參數中的value 36 if (v != null) { 37 old.value = v;//計算出的新value值不爲null則覆蓋尋找到結點的value值 38 afterNodeAccess(old); 39 } 40 else 41 removeNode(hash, key, null, false, true);//計算出的新value值爲null則移除找到的結點 42 return v; 43 } 44 if (value != null) {//沒有尋找到key相等的結點 45 if (t != null) 46 t.putTreeVal(this, tab, hash, key, value);//樹中新增結點 47 else { 48 tab[i] = newNode(hash, key, value, first);//新建結點插入到鏈表頭部 49 if (binCount >= TREEIFY_THRESHOLD - 1) 50 treeifyBin(tab, hash);//新增結點後鏈表長度達到TREEIFY_THRESHOLD則轉爲樹 51 } 52 ++modCount;//新增結點後執行 53 ++size; 54 afterNodeInsertion(true); 55 } 56 return value; 57 }
兩個批量操做不難理解,一樣要保證過程當中沒有其餘線程修改了對象的元素個數
1 public void forEach(BiConsumer<? super K, ? super V> action) { 2 Node<K,V>[] tab; 3 if (action == null) 4 throw new NullPointerException(); 5 if (size > 0 && (tab = table) != null) { 6 int mc = modCount; 7 for (int i = 0; i < tab.length; ++i) { 8 for (Node<K,V> e = tab[i]; e != null; e = e.next) 9 action.accept(e.key, e.value);//對每一個結點執行對應的操做 10 } 11 if (modCount != mc) 12 throw new ConcurrentModificationException();//有其餘線程修改了HashMap中的元素個數時拋錯 13 } 14 } 15 public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) { 16 Node<K,V>[] tab; 17 if (function == null) 18 throw new NullPointerException(); 19 if (size > 0 && (tab = table) != null) { 20 int mc = modCount; 21 for (int i = 0; i < tab.length; ++i) { 22 for (Node<K,V> e = tab[i]; e != null; e = e.next) { 23 e.value = function.apply(e.key, e.value); 24 } 25 } 26 if (modCount != mc) 27 throw new ConcurrentModificationException(); 28 } 29 }
HashMap實現了clone方法,能夠產生一個新的徹底同樣的HashMap
1 public Object clone() { 2 HashMap<K,V> result; 3 try { 4 result = (HashMap<K,V>)super.clone();//產生一個複製HashMap 5 } catch (CloneNotSupportedException e) { 6 // 由於HashMap支持clone方法,應該不會拋出這個錯誤 7 throw new InternalError(e); 8 } 9 result.reinitialize();//初始化參數值,全部集合數組都設爲null 10 result.putMapEntries(this, false);//將本來集合中的鍵值對都插入到新產生的Map中 11 return result; 12 }
capacity這個方法先看table是否爲null,不爲null直接返回table.length。而後看threshold是否爲0,不爲0返回threshold不然返回默認容量16
1 final int capacity() { 2 return (table != null) ? table.length : 3 (threshold > 0) ? threshold : 4 DEFAULT_INITIAL_CAPACITY; 5 }
HashMap的序列化方法一樣利用的是ObjectOutputStream
1 private void writeObject(java.io.ObjectOutputStream s) 2 throws IOException { 3 int buckets = capacity(); 4 // 先寫入數組大小和集合內元素的個數 5 s.defaultWriteObject(); 6 s.writeInt(buckets); 7 s.writeInt(size); 8 internalWriteEntries(s); 9 } 10 // 只有writeObject會調用這個方法 11 void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { 12 Node<K,V>[] tab; 13 if (size > 0 && (tab = table) != null) { 14 for (int i = 0; i < tab.length; ++i) {//遍歷整個數組,按照鏈表順序寫入key和value值 15 for (Node<K,V> e = tab[i]; e != null; e = e.next) { 16 s.writeObject(e.key); 17 s.writeObject(e.value); 18 } 19 } 20 } 21 }
序列化輸入是經過ObjectInputStreams,loadFactor必定會限制在0.25-4.0之間,而threshold是根據size/loadFactor + 1.0而後計算出大於等於該值的最小2的指數次冪。
1 private void readObject(java.io.ObjectInputStream s) 2 throws IOException, ClassNotFoundException { 3 // Read in the threshold (ignored), loadfactor, and any hidden stuff 4 s.defaultReadObject(); 5 reinitialize(); 6 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 7 throw new InvalidObjectException("Illegal load factor: " + 8 loadFactor); 9 s.readInt(); // 讀取數組大小 10 int mappings = s.readInt(); // 讀取元素個數 11 if (mappings < 0) 12 throw new InvalidObjectException("Illegal mappings count: " + 13 mappings); 14 else if (mappings > 0) { // (if zero, use defaults) 15 // loadFactor必定在0.25-4.0之間 16 float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f); 17 float fc = (float)mappings / lf + 1.0f; 18 int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? 19 DEFAULT_INITIAL_CAPACITY : 20 (fc >= MAXIMUM_CAPACITY) ? 21 MAXIMUM_CAPACITY : 22 tableSizeFor((int)fc));//threshold的值在不超過範圍的狀況下設定爲剛好大於等於size/loadFactor + 1的2的指數 23 float ft = (float)cap * lf; 24 threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? 25 (int)ft : Integer.MAX_VALUE); 26 @SuppressWarnings({"rawtypes","unchecked"}) 27 Node<K,V>[] tab = (Node<K,V>[])new Node[cap]; 28 table = tab; 29 30 // Read the keys and values, and put the mappings in the HashMap 31 for (int i = 0; i < mappings; i++) { 32 @SuppressWarnings("unchecked") 33 K key = (K) s.readObject(); 34 @SuppressWarnings("unchecked") 35 V value = (V) s.readObject(); 36 putVal(hash(key), key, value, false, false);//從輸入流中得去key和value值並經過putVal插入 37 } 38 } 39 }