偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

深入理解HashMap

企業(yè)動(dòng)態(tài)
本文主要描述了HashMap的結(jié)構(gòu),和hashmap中hash函數(shù)的實(shí)現(xiàn),以及該實(shí)現(xiàn)的特性,同時(shí)描述了hashmap中resize帶來(lái)性能消耗的根本原因,以及將普通的域模型對(duì)象作為key的基本要求。

1、hashmap的數(shù)據(jù)結(jié)構(gòu)

要知道hashmap是什么,首先要搞清楚它的數(shù)據(jù)結(jié)構(gòu),在java編程語(yǔ)言中,最基本的結(jié)構(gòu)就是兩種,一個(gè)是數(shù)組,另外一個(gè)是模擬指針(引用),所有的數(shù)據(jù)結(jié)構(gòu)都可以用這兩個(gè)基本結(jié)構(gòu)來(lái)構(gòu)造的,hashmap也不例外。Hashmap實(shí)際上是一個(gè)數(shù)組和鏈表的結(jié)合體(在數(shù)據(jù)結(jié)構(gòu)中,一般稱(chēng)之為“鏈表散列“),請(qǐng)看下圖(橫排表示數(shù)組,縱排表示數(shù)組元素【實(shí)際上是一個(gè)鏈表】)。

鏈表

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

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

  1. static class Entry<K,V> implements Map.Entry<K,V> {   
  2.         final K key;   
  3.         V value;   
  4.         final int hash;   
  5.         Entry<K,V> next;   
  6. ..........   

上面的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)位置的某一元素,然后通過(guò)key的equals方法在對(duì)應(yīng)位置的鏈表中找到需要的元素。從這里我們可以想象得到,如果每個(gè)位置上的鏈表只有一個(gè)元素,那么hashmap的get效率將是***的,但是理想總是美好的,現(xiàn)實(shí)總是有困難需要我們?nèi)タ朔?,哈哈~

2、hash算法

我們可以看到在hashmap中要找到某個(gè)元素,需要根據(jù)key的hash值來(lái)求得對(duì)應(yīng)數(shù)組中的位置。如何計(jì)算這個(gè)位置就是hash算法。前面說(shuō)過(guò)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ù)組長(zhǎng)度取模運(yùn)算,這樣一來(lái),元素的分布相對(duì)來(lái)說(shuō)是比較均勻的。但是,“模”運(yùn)算的消耗還是比較大的,能不能找一種更快速,消耗更小的方式那?java中時(shí)這樣做的,

  1. static int indexFor(int h, int length) {   
  2.        return h & (length-1);   

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

看下圖,左邊兩組是數(shù)組長(zhǎng)度為16(2的4次方),右邊兩組是數(shù)組長(zhǎng)度為15。兩組的hashcode均為8和9,但是很明顯,當(dāng)它們和1110“與”的時(shí)候,產(chǎn)生了相同的結(jié)果,也就是說(shuō)它們會(huì)定位到數(shù)組中的同一個(gè)位置上去,這就產(chǎn)生了碰撞,8和9會(huì)被放到同一個(gè)鏈表上,那么查詢(xún)的時(shí)候就需要遍歷這個(gè)鏈表,得到8或者9,這樣就降低了查詢(xún)的效率。同時(shí),我們也可以發(fā)現(xiàn),當(dāng)數(shù)組長(zhǎng)度為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ù)組長(zhǎng)度小了很多,這意味著進(jìn)一步增加了碰撞的幾率,減慢了查詢(xún)的效率!

鏈表

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

說(shuō)到這里,我們?cè)倩仡^看一下hashmap中默認(rèn)的數(shù)組大小是多少,查看源代碼可以得知是16,為什么是16,而不是15,也不是20呢,看到上面annegu的解釋之后我們就清楚了吧,顯然是因?yàn)?6是2的整數(shù)次冪的原因,在小數(shù)據(jù)量的情況下16比15和20更能減少key之間的碰撞,而加快查詢(xún)的效率。

所以,在存儲(chǔ)大容量數(shù)據(jù)的時(shí)候,***預(yù)先指定hashmap的size為2的整數(shù)次冪次方。就算不指定的話,也會(huì)以大于且最接近指定值大小的2次冪來(lái)初始化的,代碼如下(HashMap的構(gòu)造方法中):

  1. // Find a power of 2 >= initialCapacity   
  2. int capacity = 1;   
  3. while (capacity < initialCapacity)    
  4.     capacity <<= 1; 

3、hashmap的resize

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

4、key的hashcode與equals方法改寫(xiě)

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

Hashmap的key可以是任何類(lèi)型的對(duì)象,例如User這種對(duì)象,為了保證兩個(gè)具有相同屬性的user的hashcode相同,我們就需要改寫(xiě)hashcode方法,比方把hashcode值的計(jì)算與User對(duì)象的id關(guān)聯(lián)起來(lái),那么只要user對(duì)象擁有相同id,那么他們的hashcode也能保持一致了,這樣就可以找到在hashmap數(shù)組中的位置了。如果這個(gè)位置上有多個(gè)元素,還需要用key的equals方法在對(duì)應(yīng)位置的鏈表中找到需要的元素,所以只改寫(xiě)了hashcode方法是不夠的,equals方法也是需要改寫(xiě)滴~當(dāng)然啦,按正常思維邏輯,equals方法一般都會(huì)根據(jù)實(shí)際的業(yè)務(wù)內(nèi)容來(lái)定義,例如根據(jù)user對(duì)象的id來(lái)判斷兩個(gè)user是否相等。

在改寫(xiě)equals方法的時(shí)候,需要滿(mǎn)足以下三點(diǎn):

(1) 自反性:就是說(shuō)a.equals(a)必須為true。

(2) 對(duì)稱(chēng)性:就是說(shuō)a.equals(b)=true的話,b.equals(a)也必須為true。

(3) 傳遞性:就是說(shuō)a.equals(b)=true,并且b.equals(c)=true的話,a.equals(c)也必須為true。

通過(guò)改寫(xiě)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帶來(lái)性能消耗的根本原因,以及將普通的域模型對(duì)象作為key的基本要求。尤其是hash函數(shù)的實(shí)現(xiàn),可以說(shuō)是整個(gè)HashMap的精髓所在,只有真正理解了這個(gè)hash函數(shù),才可以說(shuō)對(duì)HashMap有了一定的理解。

【本文為51CTO專(zhuān)欄作者“王森豐”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)注明出處】

責(zé)任編輯:趙寧寧 來(lái)源: 神算子
相關(guān)推薦

2010-06-01 15:25:27

JavaCLASSPATH

2020-07-21 08:26:08

SpringSecurity過(guò)濾器

2023-10-19 11:12:15

Netty代碼

2021-02-17 11:25:33

前端JavaScriptthis

2009-09-25 09:14:35

Hibernate日志

2013-09-22 14:57:19

AtWood

2020-09-23 10:00:26

Redis數(shù)據(jù)庫(kù)命令

2019-06-25 10:32:19

UDP編程通信

2017-01-10 08:48:21

2024-02-21 21:14:20

編程語(yǔ)言開(kāi)發(fā)Golang

2025-05-06 00:43:00

MySQL日志文件MIXED 3

2017-08-15 13:05:58

Serverless架構(gòu)開(kāi)發(fā)運(yùn)維

2025-06-05 05:51:33

2015-11-04 09:57:18

JavaScript原型

2013-06-14 09:27:51

Express.jsJavaScript

2021-04-20 23:25:16

執(zhí)行函數(shù)變量

2011-04-11 16:48:12

Solaris權(quán)限

2024-03-12 00:00:00

Sora技術(shù)數(shù)據(jù)

2022-11-04 09:43:05

Java線程

2021-05-13 21:27:24

ThreadLocal多線程多線程并發(fā)安全
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)