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

深入Java集合學(xué)習(xí)系列:HashMap的實(shí)現(xiàn)原理(1)

系統(tǒng) 2720 0

1.??? HashMap 概述:

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

?

2.??? HashMap 的數(shù)據(jù)結(jié)構(gòu):

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

深入Java集合學(xué)習(xí)系列:HashMap的實(shí)現(xiàn)原理(1)

?? 從上圖中可以看出, HashMap 底層就是一個(gè)數(shù)組結(jié)構(gòu),數(shù)組中的每一項(xiàng)又是一個(gè)鏈表。當(dāng)新建一個(gè) HashMap 的時(shí)候,就會(huì)初始化一個(gè)數(shù)組。

?? 源碼如下:

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 就是數(shù)組中的元素,每個(gè) ? Map.Entry ? 其實(shí)就是一個(gè) key-value 對,它持有一個(gè)指向下一個(gè)元素的引用,這就構(gòu)成了鏈表。

?

3.??? HashMap 的存取實(shí)現(xiàn):

?? 1) 存儲(chǔ):

?

Java代碼
  1. public ?V?put(K?key,?V?value)?{??
  2. ???? //?HashMap允許存放null鍵和null值。 ??
  3. ???? //?當(dāng)key為null時(shí),調(diào)用putForNullKey方法,將value放置在數(shù)組第一個(gè)位置。 ??
  4. ???? if ?(key?==? null )??
  5. ???????? return ?putForNullKey(value);??
  6. ???? //?根據(jù)key的keyCode重新計(jì)算hash值。 ??
  7. ???? int ?hash?=?hash(key.hashCode());??
  8. ???? //?搜索指定hash值在對應(yīng)table中的索引。 ??
  9. ???? int ?i?=?indexFor(hash,?table.length);??
  10. ???? //?如果?i?索引處的?Entry?不為?null,通過循環(huán)不斷遍歷?e?元素的下一個(gè)元素。 ??
  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. }??

?

?

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

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

?

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

?

?

?? 當(dāng)系統(tǒng)決定存儲(chǔ) HashMap 中的 key-value 對時(shí),完全沒有考慮 Entry 中的 value ,僅僅只是根據(jù) key 來計(jì)算并決定每個(gè) Entry 的存儲(chǔ)位置。我們完全可以把 ? Map ? 集合中的 ? value ? 當(dāng)成 ? key ? 的附屬,當(dāng)系統(tǒng)決定了 ? key ? 的存儲(chǔ)位置之后, value ? 隨之保存在那里即可。

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

?

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

?

?

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

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

?

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

?

?

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

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

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

當(dāng) length 總是 ? 2 ? n 次方時(shí), h& (length-1) 運(yùn)算等價(jià)于對 length 取模,也就是 h%length ,但是 & % 具有更高的效率。

?? ? 這看上去很簡單,其實(shí)比較有玄機(jī)的,我們舉個(gè)例子來說明:

?? ? 假設(shè)數(shù)組長度分別為 15 16 ,優(yōu)化后的 hash 碼分別為 8 9 ,那么 & 運(yùn)算后的結(jié)果如下:

?????? ? 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

??

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

???

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

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

?

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

??

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

?

4.??? HashMap resize rehash ):

?? ? 當(dāng) HashMap 中的元素越來越多的時(shí)候, hash 沖突的幾率也就越來越高,因?yàn)閿?shù)組的長度是固定的。所以為了提高查詢的效率,就要對 HashMap 的數(shù)組進(jìn)行擴(kuò)容,數(shù)組擴(kuò)容這個(gè)操作也會(huì)出現(xiàn)在 ArrayList 中,這是一個(gè)常用的操作,而在 HashMap 數(shù)組擴(kuò)容之后,最消耗性能的點(diǎn)就出現(xiàn)了:原數(shù)組中的數(shù)據(jù)必須重新計(jì)算其在新數(shù)組中的位置,并放進(jìn)去,這就是 resize

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

?

深入Java集合學(xué)習(xí)系列:HashMap的實(shí)現(xiàn)原理(1)


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

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

【本文對您有幫助就好】

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

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 国产深夜福利 | 久操视频在线免费观看 | 亚洲一级成人 | 韩国19禁青草福利视频在线 | 特级毛片s级全部免费 | 中文字幕综合网 | 四虎网站在线观看 | 欧美啪啪毛片一区二区 | 欧洲在线免费视频 | 美女被羞羞在线观看 | 亚洲欧美另类在线 | 色综合小说天天综合网 | 精品一区二区三区影片 | 奇米影视777中文久久爱 | 九九热国产视频 | 欧美无吗 | 欧美成人毛片免费视频 | 亚洲综合图区 | 久草经典视频 | 99久久综合狠狠综合久久一区 | 黑丝毛片| 欧美特级大片 | 久久精品是免费100 久久精品首页 | 九九精品热| 全部免费的毛片在线看青青 | videoxxoo欧美老师| 九九热这里都是精品 | 爆操白虎逼 | 久久美剧免费在线观看 | 国内视频自拍在线视频 | 亚洲在线免费视频 | 婷婷春色 | 亚洲精品高清久久 | 伊人婷婷色香五月综合缴缴情 | 波多野结衣亚洲 | 好吊妞视频一区二区 | 五月婷婷伊人 | 欧美在线观看一区二区 | 欧美一级a俄罗斯毛片 | 在线观看免费情网站大全 | 元龙第三季免费观看 |