日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入了解HashMap

發(fā)布時(shí)間:2025/3/11 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入了解HashMap 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

什么是hash? 哈希算法將任意長度的二進(jìn)制值映射為較短的固定長度的二進(jìn)制值,這個(gè)小的二進(jìn)制值稱為哈希值。哈希值是一段數(shù)據(jù)唯一且極其緊湊的數(shù)值表示形式。如果散列一段明文而且哪怕只更改該段落的一個(gè)字母,隨后的哈希都將產(chǎn)生不同的值。要找到散列為同一個(gè)值的兩個(gè)不同的輸入,在計(jì)算上是不可能的,所以數(shù)據(jù)的哈希值可以檢驗(yàn)數(shù)據(jù)的完整性。一般用于快速查找和加密算法。
1、任意長度的二進(jìn)制值 2、映射關(guān)系(哈希算法,數(shù)學(xué)的一些列算法) 3、固定的二進(jìn)制值(哈希值)
任意長度的二進(jìn)制值 和 固定長度的二進(jìn)制值 是一一對(duì)應(yīng)關(guān)系 固定長度的二進(jìn)制值就相當(dāng)于一個(gè)任意長度的二進(jìn)制值的一個(gè)摘要 固定長度的二進(jìn)制值 相當(dāng)于一個(gè)關(guān)鍵字key
為什么這樣做? 舉個(gè)例子,比如這里有一萬首歌,給你一首新的歌X,要求你確認(rèn)這首歌是否在那一萬首歌之內(nèi)。無疑,將一萬首歌一個(gè)一個(gè)比對(duì)非常慢。但如果存在一種方式,能將一萬首歌的每首數(shù)據(jù)濃縮到一個(gè)數(shù)字(稱為哈希碼)中,于是得到一萬個(gè)數(shù)字,那么用同樣的算法計(jì)算新的歌X的編碼,看看歌X的編碼是否在之前那一萬個(gè)數(shù)字中,就能知道歌X是否在那一萬首歌中。
hash表特點(diǎn): 1、存取效率很高。取數(shù)據(jù)的時(shí)間復(fù)雜度為1。 hash表與其他數(shù)組如順序表,鏈表不同。 順序表,使用數(shù)組實(shí)現(xiàn),一組地址連續(xù)的存儲(chǔ)單元,數(shù)組大小有兩種方式指定,一是靜態(tài)分配,二是動(dòng)態(tài)擴(kuò)展。 查找的時(shí)候,如果知道值的下標(biāo),那么查找時(shí)間復(fù)雜度為1。但多數(shù)場景是不知道值的下標(biāo)的,只能for循環(huán)一個(gè)個(gè)比較,直到找到值位置。因此大多數(shù)場景下取值的時(shí)間復(fù)雜度為n。
鏈表的定義是遞歸的,它或者為空null,或者指向另一個(gè)節(jié)點(diǎn)node的引用,這個(gè)節(jié)點(diǎn)含有下一個(gè)節(jié)點(diǎn)或鏈表的引用。 與順序存儲(chǔ)相比,允許存儲(chǔ)空間不連續(xù),插入刪除時(shí)不需要移動(dòng)大量的元素,只需修改指針即可,但查找某個(gè)元素,只能從頭遍歷整個(gè)鏈表。查找時(shí)間復(fù)雜度為n。
hash表 傳入一個(gè)key,把key代入哈希函數(shù)里,通過計(jì)算就能得到一個(gè)哈希值,而該哈希值就是value的下標(biāo),由此找到value值,查找時(shí)間復(fù)雜度為1。
---以下是轉(zhuǎn)載內(nèi)容---

原文地址:http://www.iteye.com/topic/539465

?

Hashmap是一種非常常用的、應(yīng)用廣泛的數(shù)據(jù)類型,最近研究到相關(guān)的內(nèi)容,就正好復(fù)習(xí)一下。網(wǎng)上關(guān)于hashmap的文章很多,但到底是自己學(xué)習(xí)的總結(jié),就發(fā)出來跟大家一起分享,一起討論。?


1、hashmap的數(shù)據(jù)結(jié)構(gòu)?
要知道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ù)組和鏈表的結(jié)合體(在數(shù)據(jù)結(jié)構(gòu)中,一般稱之為“鏈表散列“),請看下圖(橫排表示數(shù)組,縱排表示數(shù)組元素【實(shí)際上是一個(gè)鏈表】)。
?



從圖中我們可以看到一個(gè)hashmap就是一個(gè)數(shù)組結(jié)構(gòu),當(dāng)新建一個(gè)hashmap的時(shí)候,就會(huì)初始化一個(gè)數(shù)組。我們來看看java代碼:?

Java代碼??
  • /**?
  • ?????*?The?table,?resized?as?necessary.?Length?MUST?Always?be?a?power?of?two.?
  • ?????*??FIXME?這里需要注意這句話,至于原因后面會(huì)講到?
  • ?????*/??
  • ????transient?Entry[]?table;??

  • Java代碼??
  • static?class?Entry<K,V>?implements?Map.Entry<K,V>?{??
  • ????????final?K?key;??
  • ????????V?value;??
  • ????????final?int?hash;??
  • ????????Entry<K,V>?next;??
  • ..........??
  • }??


  • ??????? 上面的Entry就是數(shù)組中的元素,它持有一個(gè)指向下一個(gè)元素的引用,這就構(gòu)成了鏈表。?
    ???????
    ? 當(dāng)我們往hashmap中put元素的時(shí)候,先根據(jù)key的hash值得到這個(gè)元素在數(shù)組中的位置(即下標(biāo)),然后就可以把這個(gè)元素放到對(duì)應(yīng)的位置中了。如果這個(gè)元素所在的位子上已經(jīng)存放有其他元素了,那么在同一個(gè)位子上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。從hashmap中g(shù)et元素時(shí),首先計(jì)算key的hashcode,找到數(shù)組中對(duì)應(yīng)位置的某一元素,然后通過key的equals方法在對(duì)應(yīng)位置的鏈表中找到需要的元素。從這里我們可以想象得到,如果每個(gè)位置上的鏈表只有一個(gè)元素,那么hashmap的get效率將是最高的,但是理想總是美好的,現(xiàn)實(shí)總是有困難需要我們?nèi)タ朔?#xff0c;哈哈~?

    2、hash算法?
    我們可以看到在hashmap中要找到某個(gè)元素,需要根據(jù)key的hash值來求得對(duì)應(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í)候,馬上就可以知道對(duì)應(yīng)位置的元素就是我們要的,而不用再去遍歷鏈表。?

    所以我們首先想到的就是把hashcode對(duì)數(shù)組長度取模運(yùn)算,這樣一來,元素的分布相對(duì)來說是比較均勻的。但是,“模”運(yùn)算的消耗還是比較大的,能不能找一種更快速,消耗更小的方式那?java中時(shí)這樣做的,
    ?

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


  • 首先算得key得hashcode值,然后跟數(shù)組的長度-1做一次“與”運(yùn)算(&)。看上去很簡單,其實(shí)比較有玄機(jī)。比如數(shù)組的長度是2的4次方,那么hashcode就會(huì)和2的4次方-1做“與”運(yùn)算。很多人都有這個(gè)疑問,為什么hashmap的數(shù)組初始化大小都是2的次方大小時(shí),hashmap的效率最高,我以2的4次方舉例,來解釋一下為什么數(shù)組大小為2的冪時(shí)hashmap訪問的性能最高。?

    ???????? 看下圖,左邊兩組是數(shù)組長度為16(2的4次方),右邊兩組是數(shù)組長度為15。兩組的hashcode均為8和9,但是很明顯,當(dāng)它們和1110“與”的時(shí)候,產(chǎn)生了相同的結(jié)果,也就是說它們會(huì)定位到數(shù)組中的同一個(gè)位置上去,這就產(chǎn)生了碰撞,8和9會(huì)被放到同一個(gè)鏈表上,那么查詢的時(shí)候就需要遍歷這個(gè)鏈表,得到8或者9,這樣就降低了查詢的效率。同時(shí),我們也可以發(fā)現(xiàn),當(dāng)數(shù)組長度為15的時(shí)候,hashcode的值會(huì)與14(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ù)組長度為2的n次冪的時(shí)候,不同的key算得得index相同的幾率較小,那么數(shù)據(jù)在數(shù)組上分布就比較均勻,也就是說碰撞的幾率小,相對(duì)的,查詢的時(shí)候就不用遍歷某個(gè)位置上的鏈表,這樣查詢效率也就較高了。?
    ????????? 說到這里,我們再回頭看一下hashmap中默認(rèn)的數(shù)組大小是多少,查看源代碼可以得知是16,為什么是16,而不是15,也不是20呢,看到上面annegu的解釋之后我們就清楚了吧,顯然是因?yàn)?6是2的整數(shù)次冪的原因,在小數(shù)據(jù)量的情況下16比15和20更能減少key之間的碰撞,而加快查詢的效率。?

    所以,在存儲(chǔ)大容量數(shù)據(jù)的時(shí)候,最好預(yù)先指定hashmap的size為2的整數(shù)次冪次方。就算不指定的話,也會(huì)以大于且最接近指定值大小的2次冪來初始化的,代碼如下(HashMap的構(gòu)造方法中):
    ?
    Java代碼??
  • //?Find?a?power?of?2?>=?initialCapacity??
  • ????????int?capacity?=?1;??
  • ????????while?(capacity?<?initialCapacity)???
  • ????????????capacity?<<=?1;??

  • 3、hashmap的resize?

    ?????? 當(dāng)hashmap中的元素越來越多的時(shí)候,碰撞的幾率也就越來越高(因?yàn)閿?shù)組的長度是固定的),所以為了提高查詢的效率,就要對(duì)hashmap的數(shù)組進(jìn)行擴(kuò)容,數(shù)組擴(kuò)容這個(gè)操作也會(huì)出現(xiàn)在ArrayList中,所以這是一個(gè)通用的操作,很多人對(duì)它的性能表示過懷疑,不過想想我們的“均攤”原理,就釋然了,而在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,也就是說,默認(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ù)能夠有效的提高h(yuǎn)ashmap的性能。比如說,我們有1000個(gè)元素new HashMap(1000), 但是理論上來講new HashMap(1024)更合適,不過上面annegu已經(jīng)說過,即使是1000,hashmap也自動(dòng)會(huì)將其設(shè)置為1024。 但是new HashMap(1024)還不是更合適的,因?yàn)?.75*1000 < 1000, 也就是說為了讓0.75 * size > 1000, 我們必須這樣new HashMap(2048)才最合適,既考慮了&的問題,也避免了resize的問題。?


    4、key的hashcode與equals方法改寫?
    在第一部分hashmap的數(shù)據(jù)結(jié)構(gòu)中,annegu就寫了get方法的過程:首先計(jì)算key的hashcode,找到數(shù)組中對(duì)應(yīng)位置的某一元素,然后通過key的equals方法在對(duì)應(yīng)位置的鏈表中找到需要的元素。所以,hashcode與equals方法對(duì)于找到對(duì)應(yīng)元素是兩個(gè)關(guān)鍵方法。?

    Hashmap的key可以是任何類型的對(duì)象,例如User這種對(duì)象,為了保證兩個(gè)具有相同屬性的user的hashcode相同,我們就需要改寫hashcode方法,比方把hashcode值的計(jì)算與User對(duì)象的id關(guān)聯(lián)起來,那么只要user對(duì)象擁有相同id,那么他們的hashcode也能保持一致了,這樣就可以找到在hashmap數(shù)組中的位置了。如果這個(gè)位置上有多個(gè)元素,還需要用key的equals方法在對(duì)應(yīng)位置的鏈表中找到需要的元素,所以只改寫了hashcode方法是不夠的,equals方法也是需要改寫滴~當(dāng)然啦,按正常思維邏輯,equals方法一般都會(huì)根據(jù)實(shí)際的業(yè)務(wù)內(nèi)容來定義,例如根據(jù)user對(duì)象的id來判斷兩個(gè)user是否相等。?
    在改寫equals方法的時(shí)候,需要滿足以下三點(diǎn):?
    (1) 自反性:就是說a.equals(a)必須為true。?
    (2) 對(duì)稱性:就是說a.equals(b)=true的話,b.equals(a)也必須為true。?
    (3) 傳遞性:就是說a.equals(b)=true,并且b.equals(c)=true的話,a.equals(c)也必須為true。?
    通過改寫key對(duì)象的equals和hashcode方法,我們可以將任意的業(yè)務(wù)對(duì)象作為map的key(前提是你確實(shí)有這樣的需要)。
    ?

    總結(jié):?
    ??????? 本文主要描述了HashMap的結(jié)構(gòu),和hashmap中hash函數(shù)的實(shí)現(xiàn),以及該實(shí)現(xiàn)的特性,同時(shí)描述了hashmap中resize帶來性能消耗的根本原因,以及將普通的域模型對(duì)象作為key的基本要求。尤其是hash函數(shù)的實(shí)現(xiàn),可以說是整個(gè)HashMap的精髓所在,只有真正理解了這個(gè)hash函數(shù),才可以說對(duì)HashMap有了一定的理解。
    ?



    總結(jié)

    以上是生活随笔為你收集整理的深入了解HashMap的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。