原型:public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializablenode
簡介:HashMap基於散列表實現的一個key-value數據結構,可以實現經過key值快速查找。HashMap繼承自AbstractMap抽閒類,實現了Map接口。算法
以下圖所示,HashMap是利用數組與鏈表結合的形式構建的。豎列爲數組結構,默認初始數量爲16(1<<4)個,橫列爲鏈表結構用於解決散列衝突的問題。當數組中有值得元素超過了裝載因子的比例(默認爲0.75)時,會引起擴容的操做。此操做是爲了不元素過滿時引發的鏈表長度過長,從而影響查找性能。bootstrap
上圖爲jdk1.7以前的實現,jdk1.8實現方法是當某一個桶中的元素個數超過了8時,將此桶中的鏈表構建成紅黑樹。數組
一、常量說明數據結構
1 /** 2 * 默認初始容量 3 */ 4 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 5 6 /** 7 * 最大元素數量 8 */ 9 static final int MAXIMUM_CAPACITY = 1 << 30; 10 11 /** 12 * 默認裝載因子 13 */ 14 static final float DEFAULT_LOAD_FACTOR = 0.75f; 15 16 /** 17 * 當一個桶中的元素的數量大於8時,該鏈表結構可能被轉化成一棵紅黑樹,優化查找 18 */ 19 static final int TREEIFY_THRESHOLD = 8; 20 21 /** 22 * 當一個桶中的元素的數量小於6時,該樹結構被轉化成鏈表。 23 */ 24 static final int UNTREEIFY_THRESHOLD = 6; 25 26 /** 27 * 桶被樹化的另外一個條件是,當hashmap中元素個數大於4 * MIN_TREEIFY_CAPACITY 。避免調整大小和treei閾值之間的衝突。 28 */ 29 static final int MIN_TREEIFY_CAPACITY = 64;
二、變量說明app
1 /** 2 * The table, initialized on first use, and resized as 3 * necessary. When allocated, length is always a power of two. 4 * (We also tolerate length zero in some operations to allow 5 * bootstrapping mechanics that are currently not needed.) 6 */ 7 transient Node<K,V>[] table; 8 9 /** 10 * Holds cached entrySet(). Note that AbstractMap fields are used 11 * for keySet() and values(). 12 */ 13 transient Set<Map.Entry<K,V>> entrySet; 14 15 /** 16 * HashMap中當前元素個數 17 */ 18 transient int size; 19 20 /** 21 * HashMap對象被修改次數,防止出現多個線程修改出現的線程不一致性,每次修改HashMap的值時,都會自增。當使用Iterator操做HashMap時,會用此值與Iterator內部的值作一次比較,從而判斷HashMap有沒有被其餘線程修改。故建議每次遍歷HashMap時都使用Iterator。 22 */ 23 transient int modCount; 24 25 /** 26 * 裝載因子 27 */ 28 final float loadFactor;
漏了一個變量:threshold,表明着擴容的閾值,其值爲 當前容量*裝載因子ide
三、節點數據結構函數
1 static class Node<K,V> implements Map.Entry<K,V> { 2 final int hash; //散列碼 3 final K key; //key值 4 V value; //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 // 返回該節點的散列碼 19 public final int hashCode() { 20 // key值的散列碼 冪運算 value值得散列碼 21 // 散列函數爲空值返回0,非空值則返回該對象的32位JVM地址 22 return Objects.hashCode(key) ^ Objects.hashCode(value); 23 } 24 25 public final V setValue(V newValue) { 26 V oldValue = value; 27 value = newValue; 28 return oldValue; 29 } 30 31 public final boolean equals(Object o) { 32 if (o == this) 33 return true; 34 if (o instanceof Map.Entry) { 35 Map.Entry<?,?> e = (Map.Entry<?,?>)o; 36 if (Objects.equals(key, e.getKey()) && 37 Objects.equals(value, e.getValue())) 38 return true; 39 } 40 return false; 41 } 42 }
四、經常使用方法性能
1 /** 2 * 指定初始大小以及裝載因子 3 */ 4 public HashMap(int initialCapacity, float loadFactor) { 5 if (initialCapacity < 0) 6 throw new IllegalArgumentException("Illegal initial capacity: " + 7 initialCapacity); 8 if (initialCapacity > MAXIMUM_CAPACITY) 9 initialCapacity = MAXIMUM_CAPACITY; 10 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 11 throw new IllegalArgumentException("Illegal load factor: " + 12 loadFactor); 13 this.loadFactor = loadFactor; 14 this.threshold = tableSizeFor(initialCapacity); 15 } 16 17 /** 18 * 返回一個比cap大的最小的2的冪次方整數 19 */ 20 static final int tableSizeFor(int cap) { 21 int n = cap - 1; 22 n |= n >>> 1; 23 n |= n >>> 2; 24 n |= n >>> 4; 25 n |= n >>> 8; 26 n |= n >>> 16; 27 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; 28 }
由於HashMap的容量必須是2的冪次方,因此構造方法中有關於tableSizeFor方法,得到比給定容量大的最小的2的冪次方整數,很霸氣的算法,其具體的說明可參考連接:優化
【轉載】http://blog.csdn.net/fan2012huan/article/details/51097331(寫的很詳細,很好)。
1 /** 2 * 將key-value鍵值對放入HashMap中 3 */ 4 public V put(K key, V value) { 5 return putVal(hash(key), key, value, false, true); 6 } 7 8 /** 9 * 實際put的方法 10 */ 11 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 12 boolean evict) { 13 Node<K,V>[] tab; //暫存HashMap節點數組 14 Node<K,V> p; //暫存本次要插入的節點元素數據 15 int n, i; 16 17 //若是當前HashMap爲空,則計算新分配空間 18 if ((tab = table) == null || (n = tab.length) == 0) 19 n = (tab = resize()).length; 20 // 若是計算出的新節點位置(hash & (n - 1) 等價於 hash % n)是空,則將元素直接放入 21 if ((p = tab[i = (n - 1) & hash]) == null) 22 tab[i] = newNode(hash, key, value, null); 23 //插入新的節點,並從新組織HashMap(肯定位置並決定是否樹化) 24 else { 25 Node<K,V> e; K k; 26 // 插入了重複的key值(hash碼一致且key值一致) 27 if (p.hash == hash && 28 ((k = p.key) == key || (key != null && key.equals(k)))) 29 e = p; 30 // 若是p是紅黑樹,則執行紅黑樹的插入操做 31 else if (p instanceof TreeNode) 32 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 33 // 此分支表明了鏈表的插入操做 34 else { 35 for (int binCount = 0; ; ++binCount) { 36 // 到達鏈表尾端,則執行插入 37 if ((e = p.next) == null) { 38 //插入 39 p.next = newNode(hash, key, value, null); 40 若是節點數量超過閾值,則執行樹化操做 41 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 42 treeifyBin(tab, hash); 43 break; 44 } 45 // 插入了重複值 46 if (e.hash == hash && 47 ((k = e.key) == key || (key != null && key.equals(k)))) 48 break; 49 p = e; 50 } 51 } 52 if (e != null) { // existing mapping for key 53 V oldValue = e.value; 54 if (!onlyIfAbsent || oldValue == null) 55 e.value = value; 56 afterNodeAccess(e); 57 return oldValue; 58 } 59 } 60 ++modCount; 61 //若是元素個數超過閾值,則從新分配空間,並組織數據結構 62 if (++size > threshold) 63 resize(); 64 afterNodeInsertion(evict); 65 return null; 66 } 67 68 /** 69 * 針對每一個桶從新分配空間 70 */ 71 final Node<K,V>[] resize() { 72 Node<K,V>[] oldTab = table; //暫存當前table結構 73 int oldCap = (oldTab == null) ? 0 : oldTab.length; //暫存當前桶的數量 74 int oldThr = threshold; //暫存擴容的閾值 75 int newCap, newThr = 0; //定義新的容量和閾值 76 // 若是原有HashMap不爲空 77 if (oldCap > 0) { 78 //若是容量已經達到了上限,則不擴容,返回原oldTab 79 if (oldCap >= MAXIMUM_CAPACITY) { 80 threshold = Integer.MAX_VALUE; 81 return oldTab; 82 } 83 //若是容量沒有達到上限,則將容量及擴容閾值均翻倍 84 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 85 oldCap >= DEFAULT_INITIAL_CAPACITY) 86 newThr = oldThr << 1; // double threshold 87 } 88 // 容量爲0但老的閾值大於0,則閾值保持不變 89 else if (oldThr > 0) // initial capacity was placed in threshold 90 newCap = oldThr; 91 // 若是容量與閾值均爲0,則執行初始化 92 else { // zero initial threshold signifies using defaults 93 newCap = DEFAULT_INITIAL_CAPACITY;//容量爲默認容量 94 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//閾值爲默認容量*默認閥值(16*0.75) 95 } 96 // 若是現有容量翻倍後大於最大容量或現有容量小於系統默認值(16),纔會出現新閾值=0的狀況, 97 if (newThr == 0) { 98 float ft = (float)newCap * loadFactor; 99 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 100 (int)ft : Integer.MAX_VALUE); 101 } 102 103 // 設置最新的擴容閾值 104 threshold = newThr; 105 106 // 建立擴容後的桶數組 107 @SuppressWarnings({"rawtypes","unchecked"}) 108 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 109 table = newTab; 110 111 //從新組織每一個桶內的鏈表或樹狀結構 112 if (oldTab != null) { 113 //遍歷每一個桶,分別處理每一個桶中的數據 114 for (int j = 0; j < oldCap; ++j) { 115 Node<K,V> e; 116 117 //當前桶不爲空,則需將oldTab中的內容組織到newTab中 118 if ((e = oldTab[j]) != null) { 119 oldTab[j] = null; 120 if (e.next == null) //e沒有子節點,則根據e的hash值直接將此節點放到擴容後的桶數組中合適位置 121 // 此處e.hash & (newCap - 1)等價於e.hash % newCap 122 newTab[e.hash & (newCap - 1)] = e; 123 else if (e instanceof TreeNode) //若是e是個樹型節點,則遍歷紅黑樹,將樹中的每一個節點放到新的桶數組中合適的位置,並根據新的結構決定是否須要將每一個桶作樹化 124 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 125 else { // e是個鏈式節點 126 Node<K,V> loHead = null, loTail = null; 127 Node<K,V> hiHead = null, hiTail = null; 128 Node<K,V> next; 129 do { 130 next = e.next; 131 // 由於容量擴大了二倍,則元素要麼保持不變,要麼放到index + oldCap位置 132 if ((e.hash & oldCap) == 0) {// 元素位置保持不變,先將元素放到lo鏈表中 133 if (loTail == null) 134 loHead = e; 135 else 136 loTail.next = e; 137 loTail = e; 138 } 139 else {// 元素位置須要移動,先將元素放到hi鏈表中 140 if (hiTail == null) 141 hiHead = e; 142 else 143 hiTail.next = e; 144 hiTail = e; 145 } 146 } while ((e = next) != null); 147 if (loTail != null) {//將lo鏈表放到newTab中原來(j)的位置 148 loTail.next = null; 149 newTab[j] = loHead; 150 } 151 if (hiTail != null) {//將hi鏈表放到newTab中擴容(j+oldCap)的位置 152 hiTail.next = null; 153 newTab[j + oldCap] = hiHead; 154 } 155 } 156 } 157 } 158 } 159 160 //返回最新的結構 161 return newTab; 162 }
put方法包含了HashMap的實際初始化及構建的過程,仔細研究put方法,能夠更好的瞭解HashMap這種數據結構
1 /** 2 * 根據key值獲取value值 3 */ 4 public V get(Object key) { 5 Node<K,V> e; 6 return (e = getNode(hash(key), key)) == null ? null : e.value; 7 } 8 9 /** 10 * 根據哈希碼及key值獲取value值 11 */ 12 final Node<K,V> getNode(int hash, Object key) { 13 Node<K,V>[] tab; Node<K,V> first, e; int n; K k; 14 15 //若是表不爲空且表的長度不爲空且根據hash碼定位到桶不爲空 16 if ((tab = table) != null && (n = tab.length) > 0 && 17 (first = tab[(n - 1) & hash]) != null) { 18 19 //若是該桶的第一個元素hash碼與傳參相同且key值也相同,則返回該元素節點 20 if (first.hash == hash && // always check first node 21 ((k = first.key) == key || (key != null && key.equals(k)))) 22 return first; 23 24 //若是該節點的下一個下一個節點不爲空 25 if ((e = first.next) != null) { 26 //若是該節點是樹形節點,則遍歷紅黑樹查找匹配節點 27 if (first instanceof TreeNode) 28 return ((TreeNode<K,V>)first).getTreeNode(hash, key); 29 30 //若是是鏈式節點,則遍歷該鏈表查找匹配節點 31 do { 32 if (e.hash == hash && 33 ((k = e.key) == key || (key != null && key.equals(k)))) 34 return e; 35 } while ((e = e.next) != null); 36 } 37 } 38 return null; 39 }