? ? ? ? 說是第一次,可是斷斷續(xù)續(xù)的也看了一些,今天就是對之前自己對HashMap認(rèn)識的一個總結(jié)吧!
????????Map作為我們在開發(fā)過程中使用頻率較高的一個接口,我們用到的最多的應(yīng)該是HashMap了吧,用的最多,但是最親密的事物往往最容易被忽略。今天,不管是為了接下來出去面試,還是在今后的使用過程中去提高使用它的效率,我們都有必要去進(jìn)一步深入了解它。
? ? ? ? 話不多說,我們先去看看Map這個大家庭常見的成員都有什么吧!

? ? ? ? 常見的就是這些了,大家有時間可以自己去看看,今天,HashMap才是主角!
? ? ? ? 我們先去看看HashMap中的一些常量,存在即合理,看看他們究竟有什么作用!先上圖: ? ?

? ? ? ? 接下來我們?nèi)フJ(rèn)識每一個常量:
????????1、DEFAULT_INITIAL_CAPACITY:這個指的是默認(rèn)初始容量為1<<4,也就是16,這個值必須是2的冪次方,至于為什么后邊會做解答。
????????也許有的人看著有點懵(對位運(yùn)算不是很了解),這里稍作解釋,大神可以直接略過。
????????移運(yùn)算就是在二進(jìn)制的基礎(chǔ)上對數(shù)字進(jìn)行平移。1對應(yīng)的二進(jìn)制數(shù)為0000000000000001,然后對這個值進(jìn)行位運(yùn)算(<<就是向左移),左移4位后得到的二進(jìn)制數(shù)位0000000000010000,即16,。為什么就是16呢,說一下,0001對應(yīng)的是2的0次冪,就是1;0010對應(yīng)的就是2的1次冪,就是2;以此類推,0100就是4,1000就是8,10000就是16了,具體的算法就是底數(shù)是2,指數(shù)為1所在的位數(shù)-1(或者說就是右移幾位,就是幾次方),得到的就是我們計算的結(jié)果。所以說16就是這么來的。
????????2、MAXIMUM_CAPACITY:這個指的是最大容量為1<<30,也就是2的30次方。
????????3、DEFAULT_LOAD_FACTOR:這個指的是默認(rèn)的負(fù)載因子的值為0.75。
????????負(fù)載因子:指的是一個散列表的空間的使用程度。在HashMap中利用DEFAULT_INITIAL_CAPACITY *?DEFAULT_LOAD_FACTOR去計算HashMap的大小(threshold,也就是常說的閾值),就是超過這個值就需要擴(kuò)容了。換句話來說,當(dāng)你的負(fù)載因子足夠大時,需要擴(kuò)容的機(jī)會就比較小,所以空間上相對占用的內(nèi)存就比較少,導(dǎo)致每個Entry的鏈表上的元素就會增多,查詢效率就降低了。反之,當(dāng)你的負(fù)載因子較小時,需要擴(kuò)容的機(jī)會就會比較多,所以空間上占用的內(nèi)存相對較多,Entry鏈上的元素較少,查詢效率就比較高。所以從理論上來說,在設(shè)置負(fù)載因子的時候你要考慮是追求時間上的效率還是空間上的效率。
????????4、TREEIFY_THRESHOLD:這個指的是鏈表轉(zhuǎn)成樹的一個臨界值,就是java8以后,每個桶的鏈表上的元素>8的時候,就會轉(zhuǎn)換成樹。
????????5、UNTREEIFY_THRESHOLD:這個指的是如果發(fā)現(xiàn)鏈表長度小于 6,則會由樹重新再轉(zhuǎn)化為鏈表。
????????6、MIN_TREEIFY_CAPACITY:?在鏈表轉(zhuǎn)變成樹之前會進(jìn)行判斷,如果HashMap中的大小>=64才會轉(zhuǎn)換為樹,否則只會擴(kuò)容。這是為了避免在哈希表建立初期,多個鍵值對恰好被放入了同一個鏈表中而導(dǎo)致不必要的轉(zhuǎn)化。
? ? ? ? 好了,再去看看HashMap的構(gòu)造方法吧!

? ? ? ? 我們通過對上邊的常量進(jìn)行介紹,很多邏輯已經(jīng)一目了然了吧,現(xiàn)在就去看一看一些比較中重要的方法吧:
????????1、首先是this.threshold =tableSizeFor(initialCapacity);我們點進(jìn)去看一下:

????????這個方法是我們在初始化HashMap的時候,由于HashMap的CAPACITY都是2的冪,因此這個方法用于找到大于等于初始Capacity的最小的2的冪。下面對這個運(yùn)算稍作解釋:
????????假設(shè)傳入的cap為20: ? ? ?

????????再看看上邊最后的三元運(yùn)算,得到的就是32了吧,是不是大于等于初始Capacity的最小的2的冪呢?
????????2、然后就是最后一個構(gòu)造器中的putMapEntries(m, false);點進(jìn)去看一下其實就是把另一個Map的值映射到當(dāng)前的新Map中。

????????接下里,我們?nèi)タ纯碒ashMap的內(nèi)部結(jié)構(gòu):

????????HashMap從宏觀的角度是由數(shù)組+鏈表+紅黑樹三種數(shù)據(jù)解構(gòu)實現(xiàn)的。首先,HashMap主要會通過計算key的HashCode()方法來獲取存儲位置(將key的Hash值對桶的個數(shù)進(jìn)行取模:hash%length),但是由于Hash函數(shù)有幾率促使多個key的hashCode一樣的情況,導(dǎo)致了一個數(shù)組的一個位置會出現(xiàn)多條記錄(Hash沖突),這種現(xiàn)象無法避免,于是提出了開放地址法(發(fā)生沖突,繼續(xù)尋找下一塊未被占用的存儲地址),再散列函數(shù)法,鏈地址法。HashMap采用了鏈地址法,將hashCode相同的記錄放在數(shù)組下標(biāo)相同位置上,多個hashCode相同的記錄被存儲在一條鏈表上,由于鏈表的查詢速度隨著元素的增多會不斷降低,當(dāng)元素數(shù)量足夠多的時候,就會嚴(yán)重影響到查詢效率,于是HashMap在鏈表的長度大于8的時候就會將鏈表轉(zhuǎn)換為紅黑樹。
? ? ? ? 為什么鏈表會轉(zhuǎn)紅黑樹呢?因為Map中桶的元素初始化是鏈表保存的,其查找性能是O(n),而樹結(jié)構(gòu)能將查找性能提升到O(log(n))。當(dāng)鏈表長度很小的時候,即使遍歷,速度也非??欤钱?dāng)鏈表長度不斷變長,肯定會對查詢性能有一定的影響,所以才需要轉(zhuǎn)成樹(這塊的解釋可能不是很清晰,需要大家去深究一下數(shù)據(jù)結(jié)構(gòu)了)。
????????Map中元素的結(jié)構(gòu)(Node):

????????這個地方的hash()方法,返回的只是一個hash值,首先計算key的hashCode,然后再將這個值右移16位(二進(jìn)制),最后用key計算的hashCode和友誼16位后的結(jié)果進(jìn)行異或運(yùn)算,得到了結(jié)果,怎么去得到存儲位置,我們下邊再介紹。

? ? ? ? 接下來我們看一下HashMap中一些常用的方法(講道理,源碼這個編碼習(xí)慣個人實在是受不了,給人亂七八糟的感覺,大神都這樣嗎?):
? ? ? ? get方法:

? ? ? ? put方法:

? ? ? ? resize方法:

? ? ? ? remove方法:

? ? ? ? HashMap里邊的東西還是挺多的,這里只是對一些比較表面,比較基礎(chǔ)的東西介紹了一下,還有一些方法這里沒有涉及,比如一些關(guān)于紅黑樹和鏈表之間的轉(zhuǎn)換也是很重要的,但是涉及到了數(shù)據(jù)結(jié)構(gòu)這一塊多少有點虛,還得學(xué)啊!希望后邊有機(jī)會再和大家交流吧!這次的分享就到這里吧,不知道這里對您有沒有幫助,希望大家多多批評指正吧!是學(xué)習(xí)也是蛻變,不馳于空想,不騖于虛聲 !