HashMap是一個廣泛應用於各大JAVA平臺的最最最經常使用的數據結構。<K,V>的存儲形式使HashMap備受廣大java程序員的喜歡。JDK8中HashMap發生了很大的變化,例如:以前在JDK7中存在的indexFor方法被移除了,哈希碰撞後再也不單純的使用鏈表來解決,而是使用紅黑樹+單向鏈表來解決,以前Entry<K,V>也改換爲Node<K,V>。對於紅黑樹我以爲這個是稍微有些複雜的,包括插入和刪除,紅黑樹的旋轉和5個基本原則我以爲這個是須要花時間來慢慢整理的。對數據結構的理解必定是關鍵中的關鍵。使用紅黑二叉樹的主要目的就是提高了檢索的效率,O(N)->O(logN)java
下面就我本身對HashMap的理解作一些總結。如下代碼均來自jdk 1.8.0_73node
(1)組成結構:通俗的說,HashMap就是結合了哈希表和鏈表的一種實現,在JDK8以前,之因此採用鏈表的目的實際上是在於方便解決哈希碰撞帶來的影響,提升hash值相同的插入速度。可是當一個桶轉化爲一個鏈表的時候,效率仍是會有所降低。在JDK8以後採用鏈表+紅黑樹的方式來解決哈希碰撞帶來的問題HashMap底層維護一個數組,數組中的每一項都是一個Node<K,V>。程序員
1 transient Node<K,V>[] table;//哈希表
2
3 transient Set<Map.Entry<K,V>> entrySet;//緩存
咱們向 HashMap中所存儲的數據其實是存儲在該數組當中,而HashMap中的key,value則是以Node<K,V>的形式存放在數組中。而這個Node應該放在數組的哪個位置上(這個位置一般稱爲位桶或者hash桶,即hash值相同的Entry會放在同一位置,用鏈表相連或是紅黑樹),是經過key的hashCode來計算的。數組
1 static final int hash(Object key) { 2 int h; 3 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 4 }
Map的put方法:其實看起來仍是有點麻煩的,源碼上put方法上的註釋提示了,若是key值相同則會替換。緩存
1 /** 2 * 3 * 4 * 將給定的value映射到Map上,若是該map上存在相同的key值,則會替換爲現有的值 5 * 6 * @param key key with which the specified value is to be associated 7 * @param value value to be associated with the specified key 8 * @return the previous value associated with <tt>key</tt>, or 9 * <tt>null</tt> if there was no mapping for <tt>key</tt>. 10 * (A <tt>null</tt> return can also indicate that the map 11 * previously associated <tt>null</tt> with <tt>key</tt>.) 12 */ 13 public V put(K key, V value) { 14 return putVal(hash(key), key, value, false, true); 15 }
putVal能夠劃分爲這樣的一個流程:數據結構
1)當前哈希表是否爲空,若是爲空就初始化一個app
2)而後插入這個value,要是這個數組沒有就插入,有的話就替代原先的valuethis
3)要是hash碰撞了,看看當前位置是紅黑樹仍是鏈表,而後插入。一樣,要是發現key相同則進行覆蓋。spa
4)要是當前鏈表長度超過閾值了(默認是8),就轉紅黑樹 code
這裏要有個約定:
1) 哈希表是一個大的數組,類型是Node<K,V>,規定這個是每一個桶,
2)桶裏裝的是bin
3)size是node的數量,也就是你實際存放K,V的數量
4)capacity是桶的數量,沒規定初始化容量時,通常默認是16個
1 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 2 boolean evict) { 3 Node<K,V>[] tab; Node<K,V> p; int n, i; 4 if ((tab = table) == null || (n = tab.length) == 0) 5 n = (tab = resize()).length; 6 if ((p = tab[i = (n - 1) & hash]) == null) 7 tab[i] = newNode(hash, key, value, null); 8 else { 9 Node<K,V> e; K k; 10 if (p.hash == hash && 11 ((k = p.key) == key || (key != null && key.equals(k)))) 12 e = p; 13 else if (p instanceof TreeNode) 14 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 15 else { 16 for (int binCount = 0; ; ++binCount) { 17 if ((e = p.next) == null) { 18 p.next = newNode(hash, key, value, null); 19 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 20 treeifyBin(tab, hash); 21 break; 22 } 23 if (e.hash == hash && 24 ((k = e.key) == key || (key != null && key.equals(k)))) 25 break; 26 p = e; 27 } 28 } 29 if (e != null) { // existing mapping for key 30 V oldValue = e.value; 31 if (!onlyIfAbsent || oldValue == null) 32 e.value = value; 33 afterNodeAccess(e); 34 return oldValue; 35 } 36 } 37 ++modCount; 38 if (++size > threshold) 39 resize(); 40 afterNodeInsertion(evict); 41 return null; 42 }