亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

深入Java集合學習系列:HashMap的實現原理(1)

系統 2400 0

1.??? HashMap 概述:

??? HashMap 是基于哈希表的 Map 接口的非同步實現。此實現提供所有可選的映射操作,并允許使用 null 值和 null 鍵。此類不保證映射的順序,特別是它不保證該順序恒久不變。

?

2.??? HashMap 的數據結構:

??? java 編程語言中,最基本的結構就是兩種,一個是數組,另外一個是模擬指針(引用),所有的數據結構都可以用這兩個基本結構來構造的, HashMap 也不例外。 HashMap 實際上是一個“鏈表散列”的數據結構,即數組和鏈表的結合體。

深入Java集合學習系列:HashMap的實現原理(1)

?? 從上圖中可以看出, HashMap 底層就是一個數組結構,數組中的每一項又是一個鏈表。當新建一個 HashMap 的時候,就會初始化一個數組。

?? 源碼如下:

Java代碼
  1. /** ?
  2. ?*?The?table,?resized?as?necessary.?Length?MUST?Always?be?a?power?of?two. ?
  3. ?*/ ??
  4. transient ?Entry[]?table;??
  5. ??
  6. static ? class ?Entry<K,V>? implements ?Map.Entry<K,V>?{??
  7. ???? final ?K?key;??
  8. ????V?value;??
  9. ????Entry<K,V>?next;??
  10. ???? final ? int ?hash;??
  11. ????……??
  12. }??

?

?? 可以看出, Entry 就是數組中的元素,每個 ? Map.Entry ? 其實就是一個 key-value 對,它持有一個指向下一個元素的引用,這就構成了鏈表。

?

3.??? HashMap 的存取實現:

?? 1) 存儲:

?

Java代碼
  1. public ?V?put(K?key,?V?value)?{??
  2. ???? //?HashMap允許存放null鍵和null值。 ??
  3. ???? //?當key為null時,調用putForNullKey方法,將value放置在數組第一個位置。 ??
  4. ???? if ?(key?==? null )??
  5. ???????? return ?putForNullKey(value);??
  6. ???? //?根據key的keyCode重新計算hash值。 ??
  7. ???? int ?hash?=?hash(key.hashCode());??
  8. ???? //?搜索指定hash值在對應table中的索引。 ??
  9. ???? int ?i?=?indexFor(hash,?table.length);??
  10. ???? //?如果?i?索引處的?Entry?不為?null,通過循環不斷遍歷?e?元素的下一個元素。 ??
  11. ???? for ?(Entry<K,V>?e?=?table[i];?e?!=? null ;?e?=?e.next)?{??
  12. ????????Object?k;??
  13. ???????? if ?(e.hash?==?hash?&&?((k?=?e.key)?==?key?||?key.equals(k)))?{??
  14. ????????????V?oldValue?=?e.value;??
  15. ????????????e.value?=?value;??
  16. ????????????e.recordAccess( this );??
  17. ???????????? return ?oldValue;??
  18. ????????}??
  19. ????}??
  20. ???? //?如果i索引處的Entry為null,表明此處還沒有Entry。 ??
  21. ????modCount++;??
  22. ???? //?將key、value添加到i索引處。 ??
  23. ????addEntry(hash,?key,?value,?i);??
  24. ???? return ? null ;??
  25. }??

?

?

?? 從上面的源代碼中可以看出:當我們往 HashMap put 元素的時候,先根據 key hashCode 重新計算 hash 值,根據 hash 值得到這個元素在數組中的位置(即下標),如果數組該位置上已經存放有其他元素了,那么在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。如果數組該位置上沒有元素,就直接將該元素放到此數組中的該位置上。

?? ? addEntry(hash, key, value, i) 方法根據計算出的 hash 值,將 key-value 對放在數組 table i 索引處。 addEntry ? ? HashMap ? 提供的一個包訪問權限的方法,代碼如下:

?

Java代碼
  1. void ?addEntry( int ?hash,?K?key,?V?value,? int ?bucketIndex)?{??
  2. ???? //?獲取指定?bucketIndex?索引處的?Entry? ??
  3. ????Entry<K,V>?e?=?table[bucketIndex];??
  4. ???? //?將新創建的?Entry?放入?bucketIndex?索引處,并讓新的?Entry?指向原來的?Entry ??
  5. ????table[bucketIndex]?=? new ?Entry<K,V>(hash,?key,?value,?e);??
  6. ???? //?如果?Map?中的?key-value?對的數量超過了極限 ??
  7. ???? if ?(size++?>=?threshold)??
  8. ???? //?把?table?對象的長度擴充到原來的2倍。 ??
  9. ????????resize( 2 ?*?table.length);??
  10. }??

?

?

?? 當系統決定存儲 HashMap 中的 key-value 對時,完全沒有考慮 Entry 中的 value ,僅僅只是根據 key 來計算并決定每個 Entry 的存儲位置。我們完全可以把 ? Map ? 集合中的 ? value ? 當成 ? key ? 的附屬,當系統決定了 ? key ? 的存儲位置之后, value ? 隨之保存在那里即可。

?? ? hash(int h) 方法根據 key hashCode 重新計算一次散列。此算法加入了高位計算,防止低位不變,高位變化時,造成的 hash 沖突。

?

Java代碼
  1. static ? int ?hash( int ?h)?{??
  2. ????h?^=?(h?>>>? 20 )?^?(h?>>>? 12 );??
  3. ???? return ?h?^?(h?>>>? 7 )?^?(h?>>>? 4 );??
  4. }??

?

?

?? 我們可以看到在 HashMap 中要找到某個元素,需要根據 key hash 值來求得對應數組中的位置。如何計算這個位置就是 hash 算法。前面說過 HashMap 的數據結構是數組和鏈表的結合,所以我們當然希望這個 HashMap 里面的 ? 元素位置盡量的分布均勻些,盡量使得每個位置上的元素數量只有一個,那么當我們用 hash 算法求得這個位置的時候,馬上就可以知道對應位置的元素就是我們要的,而不用再去遍歷鏈表,這樣就大大優化了查詢的效率。

?? ? 對于任意給定的對象,只要它的 ? hashCode() ? 返回值相同,那么程序調用 ? hash(int h) ? 方法所計算得到的 ? hash ? 碼值總是相同的。我們首先想到的就是把 hash 值對數組長度取模運算,這樣一來,元素的分布相對來說是比較均勻的。但是, 運算的消耗還是比較大的,在 HashMap 中是這樣做的:調用 ? indexFor(int h, int length) ? 方法來計算該對象應該保存在 ? table ? 數組的哪個索引處。 indexFor(int h, int length) ? 方法的代碼如下:

?

Java代碼
  1. static ? int ?indexFor( int ?h,? int ?length)?{??
  2. ???? return ?h?&?(length- 1 );??
  3. }??

?

?

?? 這個方法非常巧妙,它通過 ? h & (table.length -1) ? 來得到該對象的保存位,而 HashMap 底層數組的長度總是 ? 2 ? ? n ? 次方,這是 HashMap 在速度上的優化。在 ? HashMap ? 構造器中有如下代碼:

Java代碼
  1. int ?capacity?=? 1 ;??
  2. ???? while ?(capacity?<?initialCapacity)??
  3. ????????capacity?<<=? 1 ;??

?? 這段代碼保證初始化時 HashMap 的容量總是 2 n 次方,即底層數組的長度總是為 2 n 次方。

length 總是 ? 2 ? n 次方時, h& (length-1) 運算等價于對 length 取模,也就是 h%length ,但是 & % 具有更高的效率。

?? ? 這看上去很簡單,其實比較有玄機的,我們舉個例子來說明:

?? ? 假設數組長度分別為 15 16 ,優化后的 hash 碼分別為 8 9 ,那么 & 運算后的結果如下:

?????? ? h & (table.length-1) ??? ? ????????? ? ????? ? hash ???? ? ?????????????????????? ? table.length-1

?????? ? 8 & (15-1) ???????????????????????????????? ? 0100 ? ? ???? ? ??????????? ? &? ? ???????????? 1110? ????????????????? ? = ? ????????????? ? 0100

? ???? ? 9 & (15-1) ???????????????????????????????? ? 0101 ?????????????????? ? & ?????????????? 1110 ????????????????? ? = ??????????????? ? 0100

?????? ? -----------------------------------------------------------------------------------------------------------------------

?????? 8 & (16-1) ???????????????????????????????? ? 0100 ?????????????????? ? & ? ???????????? ? 1111 ?????????????????? ? = ??????????????? ? 0100

?????? 9 & (16-1) ???????????????????????????????? ? 0101 ?????????????????? ? & ? ???????????? ? 1111 ?????????????????? ? = ??????????????? ? 0101

??

?? ? 從上面的例子中可以看出:當它們和 15-1 1110 的時候,產生了相同的結果,也就是說它們會定位到數組中的同一個位置上去,這就產生了碰撞, 8 9 會被放到數組中的同一個位置上形成鏈表,那么查詢的時候就需要遍歷這個鏈 ? 表,得到 8 或者 9 ,這樣就降低了查詢的效率。同時,我們也可以發現,當數組長度為 15 的時候, hash 值會與 15-1 1110 )進行 ,那么 ? 最后一位永遠是 0 ,而 0001 , 0011 , 0101 , 1001 , 1011 0111 , 1101 這幾個位置永遠都不能存放元素了,空間浪費相當大,更糟的是這種情況中,數組可以使用的位置比數組長度小了很多,這意味著進一步增加了碰撞的幾率,減慢了查詢的效率!而當數組長度為 16 時,即為 2 n 次方時, 2 n -1 得到的二進制數的每個位上的值都為 1 ,這使得在低位上 & 時,得到的和原 hash 的低位相同,加之 hash(int h) 方法對 key hashCode 的進一步優化,加入了高位計算,就使得只有相同的 hash 值的兩個值才會被放到數組中的同一個位置上形成鏈表。

???

?? ? 所以說,當數組長度為 2 n 次冪的時候,不同的 key 算得得 index 相同的幾率較小,那么數據在數組上分布就比較均勻,也就是說碰撞的幾率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了。

?? ? 根據上面 ? put ? 方法的源代碼可以看出,當程序試圖將一個 key-value 對放入 HashMap 中時,程序首先根據該 ? key ? ? hashCode() ? 返回值決定該 ? Entry ? 的存儲位置:如果兩個 ? Entry ? ? key ? ? hashCode() ? 返回值相同,那它們的存儲位置相同。如果這兩個 ? Entry ? ? key ? 通過 equals ? 比較返回 ? true ,新添加 ? Entry ? ? value ? 將覆蓋集合中原有 ? Entry ? ? value ,但 key 不會覆蓋。如果這兩個 ? Entry ? ? key ? 通過 ? equals 比較返回 ? false ,新添加的 ? Entry ? 將與集合中原有 ? Entry ? 形成 ? Entry ? 鏈,而且新添加的 ? Entry ? 位于 ? Entry ? 鏈的頭部 —— 具體說明繼續看 addEntry() ? 方法的說明。

?? 2) ? 讀?。?

Java代碼
  1. public ?V?get(Object?key)?{??
  2. ???? if ?(key?==? null )??
  3. ???????? return ?getForNullKey();??
  4. ???? int ?hash?=?hash(key.hashCode());??
  5. ???? for ?(Entry<K,V>?e?=?table[indexFor(hash,?table.length)];??
  6. ????????e?!=? null ;??
  7. ????????e?=?e.next)?{??
  8. ????????Object?k;??
  9. ???????? if ?(e.hash?==?hash?&&?((k?=?e.key)?==?key?||?key.equals(k)))??
  10. ???????????? return ?e.value;??
  11. ????}??
  12. ???? return ? null ;??
  13. }??

?

?? 有了上面存儲時的 hash 算法作為基礎,理解起來這段代碼就很容易了。從上面的源代碼中可以看出:從 HashMap get 元素時,首先計算 key hashCode ,找到數組中對應位置的某一元素,然后通過 key equals 方法在對應位置的鏈表中找到需要的元素。

??

?? 3) ? 歸納起來簡單地說, HashMap ? 在底層將 ? key-value ? 當成一個整體進行處理,這個整體就是一個 ? Entry ? 對象。 HashMap ? 底層采用一個 ? Entry[] ? 數組來保存所有的 ? key-value ? 對,當需要存儲一個 ? Entry ? 對象時,會根據 hash 算法來決定其在數組中的存儲位置,在根據 equals 方法決定其在該數組位置上的鏈表中的存儲位置;當需要取出一個 Entry 時,也會根據 hash 算法找到其在數組中的存儲位置,再根據 equals 方法從該位置上的鏈表中取出該 Entry 。

?

4.??? HashMap resize rehash ):

?? ? HashMap 中的元素越來越多的時候, hash 沖突的幾率也就越來越高,因為數組的長度是固定的。所以為了提高查詢的效率,就要對 HashMap 的數組進行擴容,數組擴容這個操作也會出現在 ArrayList 中,這是一個常用的操作,而在 HashMap 數組擴容之后,最消耗性能的點就出現了:原數組中的數據必須重新計算其在新數組中的位置,并放進去,這就是 resize

?? ? 那么 HashMap 什么時候進行擴容呢?當 HashMap 中的元素個數超過數組大小 *loadFactor 時,就會進行數組擴容, loadFactor 的默認值為 0.75 ,這是一個折中的取值。也就是說,默認情況下,數組大小為 16 ,那么當 HashMap 中元素個數超過 16*0.75=12 的時候,就把數組的大小擴展為 ? 2*16=32 ,即擴大一倍,然后重新計算每個元素在數組中的位置,而這是一個非常消耗性能的操作,所以如果我們已經預知 HashMap 中元素的個數,那么預設元素的個數能夠有效的提高 HashMap 的性能。

?

深入Java集合學習系列:HashMap的實現原理(1)


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。。?/p>

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 日本特交大片免费观看 | 久久伊人免费视频 | 久久亚洲国产成人精品性色 | 爱爱网站免费 | 综合精品在线 | 永久黄网站色视频免费观看 | 女孕学护士一级毛片 | 欧美理论片在线观看 | 久草这里只有精品 | 欧美午夜激情影院 | julia中文字幕在线观看 | 国产亚洲片| 日日射视频| 波多野结衣中文字幕久久 | 国产精品人伦久久 | 激情综合婷婷 | 欧美一区二区久久精品 | 亚洲精品久久久久中文字幕一区 | 91视频香蕉 | 亚洲欧美日韩高清 | 国产中文字幕在线观看 | 四虎免费影视 | 国产九九 | 免费观看美女光子让男人玩 | 欧美性一区二区三区 | 亚洲综合在线一区 | 久久99精品热在线观看15 | 成人香蕉 | 日韩欧美国产偷亚洲清高 | 国产午夜亚洲精品第一区 | 热久久精品 | 国产日产欧美一区二区三区 | 久草精品免费 | 香蕉视频黄网站 | 四虎免费影院在线播放 | 精品久久久久亚洲 | 亚洲欧美精品一区二区 | 久久国产精品只做精品 | 伊人不卡久久大香线蕉综合影院 | 99精品在线免费观看 | 美日韩中文字幕 |