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

歡迎訪問(wèn) 生活随笔!

生活随笔

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

编程问答

HashMap是如何实现快速存取的

發(fā)布時(shí)間:2024/4/14 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 HashMap是如何实现快速存取的 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、存儲(chǔ)實(shí)現(xiàn):put(key,vlaue)

??????首先我們先看源碼:?

// 將“key-value”添加到HashMap中 ?
public?V put(K?key,?V?value) { ?
????// 若“key為null”,則將該鍵值對(duì)添加到table[0]中。 ?
????if?(key?==?null) ?
????????return?putForNullKey(value); ?
????// 若“key不為null”,則計(jì)算該key的哈希值,然后將其添加到該哈希值對(duì)應(yīng)的鏈表中。 ?
????int?hash?= hash(key.hashCode()); ??// 計(jì)算key hash值在table數(shù)組中的位置 ?------------ (1)
????int?i?= indexFor(hash, table.length); ?// 迭代e,從i處開(kāi)始,找到key保存的位置 ?------------ (2)
????for?(Entry<K,V> e?= table[i]; e?!=?null; e?= e.next) { ?
????????Object k; ?
????????// 若“該key”對(duì)應(yīng)的鍵值對(duì)已經(jīng)存在,則用新的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; ?
????????} ?
????} ?
?????// 若“該key”對(duì)應(yīng)的鍵值對(duì)不存在,則將“key-value”添加到table中 ?
????modCount++;
????//將key-value添加到table[i]處
????addEntry(hash, key, value, i); ?
????return?null; ?
}

????? 通過(guò)源碼我們可以清晰看到HashMap保存數(shù)據(jù)的過(guò)程為:首先判斷key是否為null,若為null,則直接調(diào)用putForNullKey方法。若不為空則先計(jì)算key的hash值,然后根據(jù)hash值搜索在table數(shù)組中的索引位置,如果table數(shù)組在該位置處有元素,則通過(guò)比較是否存在相同的key,若存在則覆蓋原來(lái)key的value,否則將該元素保存在鏈頭(最先保存的元素放在鏈尾)。若table在該處沒(méi)有元素,則直接保存。
????? 1、 先看迭代處。此處迭代原因就是為了防止存在相同的key值,若發(fā)現(xiàn)兩個(gè)hash值(key)相同時(shí),HashMap的處理方式是用新value替換舊value,這里并沒(méi)有處理key,這就解釋了HashMap中沒(méi)有兩個(gè)相同的key。
????? 2、 再看(1)、(2)處。這里是HashMap的精華所在。首先是hash方法,該方法為一個(gè)純粹的數(shù)學(xué)計(jì)算,就是計(jì)算h的hash值。

static?int?hash(int?h) { ?
return?useNewHash ? newHash(h) : oldHash(h); ?
}

useNewHash聲明如下:

private?static?final?boolean?useNewHash; ?
static?{?useNewHash?=?false; }

?

private?static?int?oldHash(int?h) { ?
????h?+= ~(h?<< 9); ?
????h?^= ?(h?>>> 14); ?
????h?+= ?(h?<< 4); ?
????h?^= ?(h?>>> 10); ?
????return?h; ?
} ???
private?static?int?newHash(int?h) { ?
????// This function ensures that hashCodes that differ only by ?
????// constant multiples at each bit position have a bounded ?
????// number of collisions (approximately 8 at default load factor). ?
????h?^= (h?>>> 20) ^ (h?>>> 12); ?
????return?h?^ (h?>>> 7) ^ (h?>>> 4); ?
}

????? 我們知道對(duì)于HashMap的table而言,數(shù)據(jù)分布需要均勻(最好每項(xiàng)都只有一個(gè)元素,這樣就可以直接找到),不能太緊也不能太松,太緊會(huì)導(dǎo)致查詢速度慢,太松則浪費(fèi)空間。計(jì)算hash值后,怎么才能保證table元素分布均與呢?我們會(huì)想到取模,但是由于取模的消耗較大,HashMap是這樣處理的:調(diào)用indexFor方法。

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

????? HashMap的底層數(shù)組長(zhǎng)度總是2的n次方,在構(gòu)造函數(shù)中存在:capacity <<= 1;這樣做總是能夠保證HashMap的底層數(shù)組長(zhǎng)度為2的n次方。當(dāng)length為2的n次方時(shí),h&(length - 1)就相當(dāng)于對(duì)length取模,而且速度比直接取模快得多,這是HashMap在速度上的一個(gè)優(yōu)化。至于為什么是2的n次方下面解釋。
????? 我們回到indexFor方法,該方法僅有一條語(yǔ)句:h&(length - 1) ?作用:均勻分布table數(shù)據(jù)和充分利用空間。
????? 這里我們假設(shè)length為16(2^n)和15,h為5、6、7。

length = 16
hlength - 1h & length -1?
5150101 & 1111 = 001015
6150110 & 1111 = 001106
7150111 & 1111 = 001117
length = 15
5140101 & 1110 = 001015
6140110 & 1110 = 001106
7140111 & 1110 = 001106

當(dāng)n=15時(shí),6和7的結(jié)果一樣,這樣表示他們?cè)趖able存儲(chǔ)的位置是相同的,也就是產(chǎn)生了碰撞,6、7就會(huì)在一個(gè)位置形成鏈表,這樣就會(huì)導(dǎo)致查詢速度降低。誠(chéng)然這里只分析三個(gè)數(shù)字不是很多,那么我們就看0-15。

hlength - 1h & length - 1?
0140000 & 1110 = 00000
1140001 & 1110 = 00000
2140010 & 1110 = 00102
3140011 & 1110 = 00102
4140100 & 1110 = 01004
5140101 & 1110 = 01004
6140110 & 1110 = 01106
7140111 & 1110 = 01106
8141000 & 1110 = 10008
9141001 & 1110 = 10008
10141010 & 1110 = 101010
11141011 & 1110 = 101010
12141100 & 1110 = 110012
13141101 & 1110 = 110012
14141110 & 1110 = 111014
15141111 & 1110 = 111014

????? 從上面的圖表中我們看到總共發(fā)生了8此碰撞,同時(shí)發(fā)現(xiàn)浪費(fèi)的空間非常大,有1、3、5、7、9、11、13、15處沒(méi)有記錄,也就是沒(méi)有存放數(shù)據(jù)。這是因?yàn)樗麄冊(cè)谂c14進(jìn)行&運(yùn)算時(shí),得到的結(jié)果最后一位永遠(yuǎn)都是0,即0001、0011、0101、0111、1001、1011、1101、1111位置處是不可能存儲(chǔ)數(shù)據(jù)的,空間減少,進(jìn)一步增加碰撞幾率,這樣就會(huì)導(dǎo)致查詢速度慢。而當(dāng)length = 16時(shí),length – 1 = 15 即1111,那么進(jìn)行低位&運(yùn)算時(shí),值總是與原來(lái)hash值相同,而進(jìn)行高位運(yùn)算時(shí),其值等于其低位值。所以說(shuō)當(dāng)length = 2^n時(shí),不同的hash值發(fā)生碰撞的概率比較小,這樣就會(huì)使得數(shù)據(jù)在table數(shù)組中分布較均勻,查詢速度也較快。
????? 這里我們?cè)賮?lái)復(fù)習(xí)put的流程:當(dāng)我們想一個(gè)HashMap中添加一對(duì)key-value時(shí),系統(tǒng)首先會(huì)計(jì)算key的hash值,然后根據(jù)hash值確認(rèn)在table中存儲(chǔ)的位置。若該位置沒(méi)有元素,則直接插入。否則迭代該處元素鏈表并依此比較其key的hash值。如果兩個(gè)hash值相等且key值相等(e.hash == hash && ((k = e.key) == key || key.equals(k))),則用新的Entry的value覆蓋原來(lái)節(jié)點(diǎn)的value。如果兩個(gè)hash值相等但key值不等 ,則將該節(jié)點(diǎn)插入該鏈表的鏈頭。具體的實(shí)現(xiàn)過(guò)程見(jiàn)addEntry方法,如下:

void?addEntry(int?hash, K key, V value,?int?bucketIndex) { ?
????// 獲取bucketIndex處的Entry
Entry<K,V> e?= table[bucketIndex]; ?
// 將新創(chuàng)建的 Entry 放入 bucketIndex 索引處,并讓新的 Entry 指向原來(lái)的 Entry
table[bucketIndex] =?new?Entry<K,V>(hash, key, value, e); ?
????// 若HashMap中元素的個(gè)數(shù)超過(guò)極限了,則容量擴(kuò)大兩倍
if?(size++ >= threshold) ?
????resize(2 * table.length); ?
}

????? 這個(gè)方法中有兩點(diǎn)需要注意:
????? 一、鏈的產(chǎn)生。
系統(tǒng)總是將新的Entry對(duì)象添加到bucketIndex處。如果bucketIndex處已經(jīng)有了對(duì)象,那么新添加的Entry對(duì)象將指向原有的Entry對(duì)象,形成一條Entry鏈,但是若bucketIndex處沒(méi)有Entry對(duì)象,也就是e==null,那么新添加的Entry對(duì)象指向null,也就不會(huì)產(chǎn)生Entry鏈了。
????? 二、擴(kuò)容問(wèn)題。
????? 隨著HashMap中元素的數(shù)量越來(lái)越多,發(fā)生碰撞的概率就越來(lái)越大,所產(chǎn)生的鏈表長(zhǎng)度就會(huì)越來(lái)越長(zhǎng),這樣勢(shì)必會(huì)影響HashMap的速度,為了保證HashMap的效率,系統(tǒng)必須要在某個(gè)臨界點(diǎn)進(jìn)行擴(kuò)容處理。該臨界點(diǎn)在當(dāng)HashMap中元素的數(shù)量等于table數(shù)組長(zhǎng)度*加載因子。但是擴(kuò)容是一個(gè)非常耗時(shí)的過(guò)程,因?yàn)樗枰匦掠?jì)算這些數(shù)據(jù)在新table數(shù)組中的位置并進(jìn)行復(fù)制處理。所以如果我們已經(jīng)預(yù)知HashMap中元素的個(gè)數(shù),那么預(yù)設(shè)元素的個(gè)數(shù)能夠有效的提高HashMap的性能。

二、讀取實(shí)現(xiàn):get(key)

????? 相對(duì)于HashMap的存而言,取就顯得比較簡(jiǎn)單了。通過(guò)key的hash值找到在table數(shù)組中的索引處的Entry,然后返回該key對(duì)應(yīng)的value即可。

// 獲取key對(duì)應(yīng)的value ?
public?V get(Object key) {
// 若為null,調(diào)用getForNullKey方法返回相對(duì)應(yīng)的value
????if?(key?==?null) ?
???? // 根據(jù)該 key 的 hashCode 值計(jì)算它的 hash 碼
????????return?getForNullKey(); ?
????// 獲取key的hash值 ?
????int?hash?= hash(key.hashCode()); ?
????// 取出 table 數(shù)組中指定索引處的值,在“該hash值對(duì)應(yīng)的鏈表”上查找“鍵值等于key”的元素 ?
????for?(Entry<K,V> e?= table[indexFor(hash, table.length)]; ?
?????????e?!=?null; ?
?????????e?= e.next) { ?
????????Object k; ?
????????//判斷key是否相同,若key與查找的key相同,則返回相對(duì)應(yīng)的value
????????if?(e.hash == hash?&& ((k?= e.key) == key?|| key.equals(k))) ?
????????????return?e.value; ?
????}
????//沒(méi)找到則返回null
????return?null; ?
}

?????

?在這里能夠根據(jù)key快速的取到value除了和HashMap的數(shù)據(jù)結(jié)構(gòu)密不可分外,還和Entry有莫大的關(guān)系,在前面就提到過(guò),HashMap在存儲(chǔ)過(guò)程中并沒(méi)有將key,value分開(kāi)來(lái)存儲(chǔ),而是當(dāng)做一個(gè)整體key-value來(lái)處理的,這個(gè)整體就是Entry對(duì)象。同時(shí)value也只相當(dāng)于key的附屬而已。在存儲(chǔ)的過(guò)程中,系統(tǒng)根據(jù)key的hashcode來(lái)決定Entry在table數(shù)組中的存儲(chǔ)位置,在取的過(guò)程中同樣根據(jù)key的hashcode取出相對(duì)應(yīng)的Entry對(duì)象。上海尚學(xué)堂java培訓(xùn)原創(chuàng),陸續(xù)java技術(shù)相關(guān)文章奉上,請(qǐng)多關(guān)注。

轉(zhuǎn)載于:https://www.cnblogs.com/shsxt/p/7822868.html

總結(jié)

以上是生活随笔為你收集整理的HashMap是如何实现快速存取的的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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