標籤:pre 系統 不為 需要 索引值 shm code 節點 zhang
HashMap中的put方法
public V put(K key, V value) { //當key為null,調用putForNullKey方法,儲存null與table第一個位置中,這是HashMap允許為null的原因 if (key == null) return putForNullKey(value); //計算key的hash值 int hash = hash(key.hashCode()); ------(1) //計算key hash 值在 table 數組中的位置 int i = indexFor(hash, table.length); ------(2) //從i出開始迭代 e,找到 key 儲存的位置 for (Entry<K, V> e = table[i]; e != null; e = e.next) { Object k; //判斷該條鏈上是否有hash值相同的(key相同) //若存在相同,則直接覆蓋value,返回舊value if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; //舊值 = 新值 e.value = value; e.recordAccess(this); return oldValue; //返回舊值 } } //修改次數增加1 modCount++; //將key、value添加至i位置處 addEntry(hash, key, value, i); return null; }
key為null:
private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(0, null, value, 0); return null; }
擷取Entry的第一個元素table[0],並基於第一個元素的next屬性開始遍曆,直到找到key為null的Entry,將其value設定為新的value值。
如果沒有找到key為null的元素,則調用如上述代碼的addEntry(0, null, value, 0);增加一個新的entry
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); }
hash方法:
static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
講解說明:
將hash函數作為已給定的hashCode的一個補充,可以提高hash函數的品質。hash品質的好壞是非常重要的,因為HashMap用2的次冪作為表的hash長度,這就容易產生衝突,因為hashCodes在低位不是不同的(hashCodes that do not differ in lower bits)。注意:Null 的key的hash值總是0,即他在table的索引為0。 讓我們通過例子來協助我們理解一下上面的話。加入說key object的hashCode()只返回三個值:31、63和95.31、63和95都是int型,所以是32位的。 31=00000000000000000000000000011111 63=00000000000000000000000000111111 95=00000000000000000000000001011111 現在加入HashMap的table長為預設值16(2^4,HashMap的長度總是2的次冪) 假如我們不用hash函數,indexFor將返回如下值: 31=00000000000000000000000000011111 => 1111=15 63=00000000000000000000000000111111 => 1111=15 95=00000000000000000000000001011111 => 1111=15為什麼會這樣?因為當我們調用indexFor函數的時候,它將執行31&15,,63&15和95&15的與操作,比如說95&15得出一下結果: 00000000000000000000000001011111 &00000000000000000000000000001111 也就是說(2^n-1)總會是一個1的序列,因此不管怎樣,最後會執行0&1的於然後得出0. 上面的例子,也就解釋了凡是在末尾全是1111的都返回相同的index,因此,儘管我們有不同的hashcode,Entry對象卻講都被存在table中index為15的位置。 倘若我們使用了hash函數,對於上面每一個hashcode,經過hash作用作用後如下: 31=00000000000000000000000000011111 => 00000000000000000000000000011110 63=00000000000000000000000000111111 => 00000000000000000000000000111100 95=00000000000000000000000001011111 => 00000000000000000000000001011010 現在在通過新的hash之後再使用indexFor將會返回: 00000000000000000000000000011110 =>1110=14 00000000000000000000000000111100 =>1100=12 00000000000000000000000001011010 =>1010=10 在使用了hash函數之後,上面的hashcodes就返回了不同的index,因此,hash函數對hashmap裡的元素進行了再分配,也就減少了衝突同時提高了效能。 hash操作最主要的目的就是在最顯著位的hashcode的差異可見,以致於hashmap的元素能夠均勻的分布在整個桶裡。 有兩點需要注意: 如果兩個key有相同的hashcode,那他們將被分配到table數組的相同index上 如果兩個key不具有相同的hashcode,那麼他們或許可能,或許也不可能被分配到table數組相同的index上。
View Code
indexFor方法:
static int indexFor(int h, int length) { return h & (length-1); }
講解說明:
這個方法有點意思,主要作用是定位hashmap裡的bucket。-------------------------------------------------------大家知道hashmap底層就是一個數組,然後數組裡每個元素裝了個鏈表。這個數組元素稱為bucket桶-------------------------------------------------------先複習一下邏輯與。0 & 0 = 0;0 & 1 = 0;1 & 0 = 0;1 & 1 = 1;第二個參數length始終為2的n次方,所以,換成位元就是 100,1000,10000,...(length -1)就是 11, 111,1111,...這樣的話,第一個參數h比第二個參數小的情況下,那結果就是h。第一個參數h比第二個參數大的情況下,如下:例:h=18 -> 10010length-1=15 -> 01111所以在第一個參數比第二個參數大的情況下等於第一參數%第二參數,取餘數
View Code
addEntry
void addEntry(int hash, K key, V value, int bucketIndex) { //擷取bucketIndex處的Entry Entry<K, V> e = table[bucketIndex]; //將新建立的 Entry 放入 bucketIndex 索引處,並讓新的 Entry 指向原來的 Entry table[bucketIndex] = new Entry<K, V>(hash, key, value, e); //若HashMap中元素的個數超過極限了,則容量擴大兩倍 if (size++ >= threshold) resize(2 * table.length); }View Code
put的流程:
當我們想一個HashMap中添加一對key-value時,系統首先會計算key的hash值,然後根據hash值確認在table中儲存的位置。
若該位置沒有元素,則直接插入。否則迭代該處元素鏈表並依此比較其key的hash值。
如果兩個hash值相等且key值相等(e.hash == hash && ((k = e.key) == key || key.equals(k))),則用新的Entry的value覆蓋原來節點的value。
如果兩個hash值相等但key值不等 ,則將該節點插入該鏈表的鏈頭(最先儲存的元素放在鏈尾),新元素設定為Entry[0],其next指標指向原有對象,即原有對象為Entry[1]
比如, 第一個索引值對A進來,通過計算其key的hash得到的i=0,記做:Entry[0] = A。一會後又進來一個索引值對B,通過計算其i也等於0,現在怎麼辦?HashMap會這樣做:B.next = A,Entry[0] = B,如果又進來C,i也等於0,那麼C.next = B,Entry[0] = C;這樣我們發現i=0的地方其實存取了A,B,C三個索引值對,他們通過next這個屬性連結在一起,也就是說數組中儲存的是最後插入的元素。
View Code
HashMap的get操作:
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k)))//-------------------1---------------- return e.value; } return null; }
當key為null時,調用getForNullKey()
private V getForNullKey() { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; }
當key不為null時,先根據hash函數得到hash值,在更具indexFor()得到i的值,迴圈遍曆鏈表,如果有:key值等於已存在的key值,則返回其value。
=======================================================================================================
JDK8的講解圖:
HashMap的put方法
=====================================================================
JDK1.8以後對hashmap進行了大量的最佳化
JDK8 因為對自己改造過的雜湊大量衝突時的紅/黑樹狀結構有信心,所以簡單一些,只是把高16位異或下來。
static int hash(int h) { return h ^ (h >>> 16); }
所以即使Key比較均勻無雜湊衝突,JDK8也比JDK7略快的原因大概於此。
存在雜湊衝突的情況,比如兩個雜湊值模數後落在同一個桶上,或者兩條不同的key有相同的雜湊值。
JDK7的做法是建一條鏈表,後插入的元素在上面,一個個地執行上面的判斷。
而JDK8則在鏈表長度達到8,而且桶數量達到64時,建一棵紅/黑樹狀結構,解決嚴重衝突時的效能問題。
http://www.cnblogs.com/chenssy/p/3521565.html
http://blog.csdn.net/zhangerqing/article/details/8193118
http://tech.meituan.com/java-hashmap.html
http://calvin1978.blogcn.com/articles/hashmap.html
http://www.importnew.com/7099.html
HashMap put,get操作