JDK1.8的HashMap源碼分析

JDK1.8之前的HashMap

在JDK1.8之前,HashMap通過(guò)散列表(哈希表)實(shí)現(xiàn),并且散列表沖突解決方案是鏈地址法(拉鏈法),也可理解為數(shù)組+鏈表實(shí)現(xiàn)。

在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ù)據(jù)結(jié)構(gòu)仔沿,即數(shù)組和鏈表的結(jié)合體坐桩。

什么是散列表?
一塊連續(xù)的存儲(chǔ)空間中于未,用以存儲(chǔ)按散列函數(shù)計(jì)算得到相應(yīng)散列地址的數(shù)據(jù)記錄撕攒。通常散列表的存儲(chǔ)空間是一個(gè)一維數(shù)組,散列地址是數(shù)組的下標(biāo)烘浦。
重溫?cái)?shù)據(jù)結(jié)構(gòu)_散列表/哈希表的查找

向HashMap存儲(chǔ)元素:
向HashMap中存儲(chǔ)某元素時(shí)候抖坪,首先hash算法根據(jù)元素的key值得到一個(gè)hash值,這個(gè)hash值就可以確定元素在數(shù)組中的位置闷叉。如果此位置沒有元素擦俐,直接存;如果已經(jīng)存在元素握侧,就要與它們的key進(jìn)行比較蚯瞧,有以下兩種情況:1.key相同的,覆蓋舊值品擎;2.key都不同埋合,形成一個(gè)以新元素為頭部的鏈表。

從HashMap獲取元素:
先根據(jù)key的hash算法得到hash值萄传,找到該元素在數(shù)組中的位置甚颂;再通過(guò)key的equals方法,在鏈表中找到對(duì)應(yīng)的元素秀菱,也就找到了key對(duì)應(yīng)的value振诬。

HashMap結(jié)構(gòu)圖

JDK1.8的HashMap

HashMap通過(guò)數(shù)組+鏈表+紅黑樹實(shí)現(xiàn),當(dāng)鏈表長(zhǎng)度超過(guò)閾值(8)時(shí)衍菱,將鏈表轉(zhuǎn)換為紅黑樹赶么,并且再次之后都用紅黑樹來(lái)存儲(chǔ)沖突的元素。

為什么JDK1.8HashMap要做如此改變脊串?
我們都知道在鏈表中查找節(jié)點(diǎn)時(shí)辫呻,會(huì)花費(fèi)O(n)的查找時(shí)間项钮。為了防止拉鏈過(guò)長(zhǎng),導(dǎo)致嚴(yán)重影響HashMap的性能刽宪,當(dāng)鏈表長(zhǎng)度超過(guò)閾值(8)時(shí)悬嗓,不再采用單鏈表形式存儲(chǔ),而是采用紅黑樹存儲(chǔ)读串,利用紅黑樹快速增刪改查的特點(diǎn)提高HashMap的性能。

jdk1.8 hashmap結(jié)構(gòu)圖

JDK1.8 HashMap源碼分析

重要的字段

    /**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     */
    transient Node<K, V>[] table;

    /**
     * The number of key-value mappings contained in this map.
     * 包含在map中鍵-值對(duì)的數(shù)量
     */
    transient int size;

    /**
     * hashmap被結(jié)構(gòu)化改變的次數(shù)
     */
    transient int modCount;

    /**
     * The next size value at which to resize (capacity * load factor).
     * 擴(kuò)容的閾值,當(dāng)size大于這個(gè)值時(shí)念赶,table數(shù)組進(jìn)行擴(kuò)容
     */
    int threshold;

    /**
     * The load factor for the hash table.
     * 
     */
    float loadFactor;

對(duì)這些重要字段的解釋:

  • table:Node[] table的初始化長(zhǎng)度length默認(rèn)值是16础钠;
  • loadFactor :負(fù)載因子默認(rèn)值是0.75;
  • threshold :是HashMap所能容納的最大數(shù)量的Node(鍵值對(duì))個(gè)數(shù)叉谜。

負(fù)載因子的定義公式:threshold = length * loadFactor旗吁。也就是說(shuō),在數(shù)組定義好長(zhǎng)度之后停局,負(fù)載因子越大很钓,所能容納的鍵值對(duì)個(gè)數(shù)越多。
當(dāng)size超過(guò)這個(gè)數(shù)目就重新resize(擴(kuò)容)董栽,擴(kuò)容后的HashMap容量是之前容量的兩倍码倦。默認(rèn)的負(fù)載因子0.75是對(duì)空間和時(shí)間效率的一個(gè)平衡選擇,建議大家不要修改锭碳,除非在時(shí)間和空間比較特殊的情況下袁稽,如果內(nèi)存空間很多而又對(duì)時(shí)間效率要求很高,可以降低負(fù)載因子Load factor的值擒抛;相反推汽,如果內(nèi)存空間緊張而對(duì)時(shí)間效率要求不高,可以增加負(fù)載因子loadFactor的值歧沪,這個(gè)值可以大于1歹撒。

  • size:包含在map中鍵-值對(duì)的實(shí)際數(shù)量。注意和table的長(zhǎng)度length诊胞、容納最大鍵值對(duì)數(shù)量threshold的區(qū)別暖夭。
  • modCount:主要用來(lái)記錄HashMap內(nèi)部結(jié)構(gòu)發(fā)生變化的次數(shù)。需要注意的是內(nèi)部結(jié)構(gòu)發(fā)生變化真正意義是元素的key從來(lái)沒有被put過(guò)厢钧。

如何確定key對(duì)應(yīng)哈希桶數(shù)組索引位置鳞尔?
通過(guò)這個(gè)Java方法運(yùn)算來(lái)獲取hash碼值:

    static final int hash(Object key) {
        int h;
        //取key的hashCode值、高位運(yùn)算
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

通過(guò)hash方法得到的hash碼值早直,還不是數(shù)組的下標(biāo)編號(hào)寥假,還差一步,這一步就是hash碼值對(duì)數(shù)組長(zhǎng)度進(jìn)行取模運(yùn)算霞扬,具體代碼如下圖紅框糕韧。

下圖是得到key在數(shù)組具體位置索引號(hào)的過(guò)程:


圖來(lái)源于網(wǎng)絡(luò)


### 重要方法分析
#### 靜態(tài)內(nèi)部類Node
Node是HashMap的一個(gè)靜態(tài)內(nèi)部類,實(shí)現(xiàn)了Map.Entry接口喻圃,表示一個(gè)映射條目或者說(shuō)是鍵值對(duì)萤彩。

static class Node<K,V> implements Map.Entry<K,V> {
//根據(jù)hash方法計(jì)算出來(lái)的值,作為定位數(shù)組位置的索引
final int hash;
final K key;
V value;
Node<K,V> next;//該Node指向的下一個(gè)Node

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
    ...

}

HashMap的put方法實(shí)現(xiàn)

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末斧拍,一起剝皮案震驚了整個(gè)濱河市雀扶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖愚墓,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件予权,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡浪册,警方通過(guò)查閱死者的電腦和手機(jī)扫腺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)村象,“玉大人笆环,你說(shuō)我怎么就攤上這事『裾撸” “怎么了躁劣?”我有些...
    開封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)籍救。 經(jīng)常有香客問我习绢,道長(zhǎng),這世上最難降的妖魔是什么蝙昙? 我笑而不...
    開封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任闪萄,我火速辦了婚禮,結(jié)果婚禮上奇颠,老公的妹妹穿的比我還像新娘败去。我一直安慰自己,他們只是感情好烈拒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開白布圆裕。 她就那樣靜靜地躺著,像睡著了一般荆几。 火紅的嫁衣襯著肌膚如雪吓妆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天吨铸,我揣著相機(jī)與錄音行拢,去河邊找鬼。 笑死诞吱,一個(gè)胖子當(dāng)著我的面吹牛舟奠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播房维,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沼瘫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了咙俩?” 一聲冷哼從身側(cè)響起耿戚,我...
    開封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后溅话,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晓锻,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡歌焦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年飞几,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片独撇。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡屑墨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纷铣,到底是詐尸還是另有隱情卵史,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布搜立,位于F島的核電站以躯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏啄踊。R本人自食惡果不足惜忧设,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望颠通。 院中可真熱鬧址晕,春花似錦、人聲如沸顿锰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)硼控。三九已至刘陶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間牢撼,已是汗流浹背匙隔。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浪默,地道東北人牡直。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像纳决,于是被迫代替她去往敵國(guó)和親碰逸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容