HashMap put,get操作

來源:互聯網
上載者:User

標籤: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操作

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.