HashMap源碼學(xué)習(xí)筆記

最近忙于各種事情,只能陸陸續(xù)續(xù)也看了一些東西,Java的HashMap應(yīng)該算比較基礎(chǔ)的東西梅尤,也是最近在看<<Redis設(shè)計(jì)與實(shí)現(xiàn)>>展东,其中也有HashMap的數(shù)據(jù)結(jié)構(gòu)赔硫,又回去看了一下Java本身實(shí)現(xiàn),這篇也就再記錄一下盐肃。

Java數(shù)據(jù)結(jié)構(gòu)中定義了Map接口爪膊,該接口有四個(gè)常用實(shí)現(xiàn)類:HashMap,?Hashtable砸王,LinkedHashMap和TreeMap推盛。

針對(duì)上面四個(gè)常用類簡(jiǎn)單的介紹一下:

1. Hashtable: 從下面的Java doc就可以看出,其本身是線程安全的谦铃,但是并發(fā)性不如concurrent中的ConcurrentHashMap耘成,而不需要線程安全時(shí)候,也推薦使用HashMap,故可以算是一個(gè)遺留類瘪菌,不推薦使用撒会。

Hashtable Java doc

2. LinkedHashMap:它是HashMap的一個(gè)子類,保存了記錄的插入順序师妙,在用Iterator遍歷LinkedHashMap時(shí)诵肛,先得到的記錄肯定是先插入的。

LinkedHashMap java doc

3. TreeMap:TreeMap實(shí)現(xiàn)SortedMap接口默穴,能夠把它保存的記錄根據(jù)鍵排序怔檩,默認(rèn)是按鍵值的升序排序,也可以指定排序的比較器蓄诽,當(dāng)用Iterator遍歷TreeMap時(shí)薛训,得到的記錄是排過序的。如果使用排序的映射若专,建議使用TreeMap许蓖。

TreeMap Java doc

4. HashMap:它根據(jù)鍵的hashCode值存儲(chǔ)數(shù)據(jù),大多數(shù)情況下可以直接定位到它的值调衰,因而具有很快的訪問速度膊爪,但遍歷順序卻是不確定的。 HashMap最多只允許一條記錄的鍵為null嚎莉,允許多條記錄的值為null米酬。HashMap非線程安全,可能會(huì)導(dǎo)致數(shù)據(jù)的不一致趋箩。如果需要滿足線程安全赃额,可以使用之前提及的ConcurrentHashMap(不建議用Hashtable)。


本篇主要簡(jiǎn)單介紹的就是HashMap的實(shí)現(xiàn)叫确,也是由于HashMap是最最常用的一個(gè)跳芳,可以滿足大部分場(chǎng)景。自己工作了一年時(shí)間竹勉,基本也只用過HashMap= =


內(nèi)部結(jié)構(gòu)

HashMap內(nèi)部的數(shù)據(jù)結(jié)構(gòu)飞盆,就是最經(jīng)典的數(shù)組+鏈表實(shí)現(xiàn)的哈希桶(JDK 1.7之前),從1.8之后次乓,鏈表節(jié)點(diǎn)數(shù)量滿足一定條件后吓歇,會(huì)自動(dòng)轉(zhuǎn)換成紅黑樹的數(shù)據(jù)結(jié)構(gòu),進(jìn)一步提高查詢效率票腰。簡(jiǎn)單來說城看,HashMap的結(jié)構(gòu)就是一個(gè)指針數(shù)組

數(shù)據(jù)結(jié)構(gòu)

圖中的黑點(diǎn)則是存放Key-Value的Node杏慰,其數(shù)據(jù)結(jié)構(gòu)如下:

Node數(shù)據(jù)結(jié)構(gòu)

其中 hash是用來定位數(shù)組索引位置测柠, next是鏈表的下一個(gè)node炼鞠。


字段

Map.put("key", "value")

在不考慮擴(kuò)容的情況下,put操作會(huì)首先計(jì)算key的hash值鹃愤,并通過取高位運(yùn)算 + 取模運(yùn)算兩步簇搅,就能計(jì)算出該key在哈希桶的位置了完域。

當(dāng)兩個(gè)key定位在了同一個(gè)位置软吐,則表示發(fā)生了Hash碰撞。因此吟税,良好的Hash算法凹耙,能夠盡量減少Hash碰撞,提高M(jìn)ap的存取效率肠仪。然而肖抱,即使很好的Hash算法,如果哈希桶的size很幸炀伞(相比于Node數(shù)量)意述,無論怎么計(jì)算,總是在這幾個(gè)位置吮蛹,也會(huì)出現(xiàn)很多碰撞荤崇。因此,解決碰撞潮针,不僅需要良好的Hash算法术荤,還需要一個(gè)良好的擴(kuò)容機(jī)制。

要討論擴(kuò)容機(jī)制每篷,就先看一下HashMap中的幾個(gè)字段(附默認(rèn)值):

capacity: Hash桶容量
load_factor: 負(fù)載因子
size:已有node數(shù)量瓣戚,modCount:內(nèi)部結(jié)構(gòu)變化次數(shù),threshold=capacity * factor: 最大node數(shù)量

從上面幾個(gè)字段可以看出焦读,當(dāng)put操作子库,使得size > threshold時(shí),HashMap就會(huì)發(fā)生擴(kuò)容矗晃。 并且從Java 動(dòng)doc可以看出仑嗅,Hash桶的大小一定是2的n次方。(正是這個(gè)限制喧兄,使得HashMap在擴(kuò)容和計(jì)算key位置的運(yùn)算效率提升了很多)


實(shí)現(xiàn)

Hash算法的實(shí)現(xiàn)无畔,其實(shí)只有下面三行代碼:

int hashcode = key.hashCode(); // 獲取hashcode

int hashInt = hashcode ^??(hashcode >>> 16 ); // 高位運(yùn)算

int index = hashInt & (length - 1) // 取模運(yùn)算,?lenght是數(shù)組大小

第二步通過hashCode()的高16位異或低16位實(shí)現(xiàn)的:(h = k.hashCode()) ^ (h >>> 16)吠冤,主要是從速度浑彰、功效、質(zhì)量來考慮的拯辙,這么做可以在數(shù)組table的length比較小的時(shí)候郭变,也能保證考慮到高低Bit都參與到Hash的計(jì)算中颜价,同時(shí)不會(huì)有太大的開銷。

第三步也是非常巧妙诉濒,因?yàn)長(zhǎng)enght是2的n次方周伦,因此length - 1 永遠(yuǎn)是n個(gè)1,其實(shí)相當(dāng)于對(duì)hashInt做了一次取模未荒,但是效率極高专挪。

下面是JDK 1.8的put代碼實(shí)現(xiàn):

JDK1.8中put實(shí)現(xiàn)

line 627-628: table為空則創(chuàng)建.

line 629-630: 計(jì)算index,并且check null片排, 如果為null寨腔, 直接創(chuàng)建一個(gè)index;

line 633-635: 如果需要put的key和該位置原來的key一樣,則直接覆蓋value率寡, 否則進(jìn)行下面的追加操作

line 676-637: 紅黑樹操作迫卢,追加Node到紅黑樹

line 638-650: 鏈表操作,追加node到鏈表冶共,并且判斷是否需要轉(zhuǎn)化為紅黑樹乾蛤。

line 661-662: 判斷是否需要擴(kuò)容


擴(kuò)容機(jī)制

擴(kuò)容機(jī)制里的算法相對(duì)也比較復(fù)雜,HashMap的線程不安全性捅僵,也正是由于擴(kuò)容時(shí)家卖,鏈表操作可能導(dǎo)致的Infinite Loop引起。因此下一篇再具體舉例說明吧命咐。順便可以一起把redis的HashMap resize機(jī)制一起說一下篡九,基本都是大同小異。


總結(jié)

本篇就從源碼角度醋奠,簡(jiǎn)單講解HashMap的基本數(shù)據(jù)結(jié)構(gòu)和關(guān)鍵操做的實(shí)現(xiàn)榛臼,以及簡(jiǎn)單介紹了擴(kuò)容機(jī)制,由于JDK1.8以后的紅黑樹窜司,導(dǎo)致擴(kuò)容的代碼更加復(fù)雜沛善,但是擴(kuò)容的算法相對(duì)于1.8之前,也有了不少優(yōu)化塞祈,不過之后也不會(huì)深入算法方便金刁,主要還是會(huì)介紹擴(kuò)容的流程和原理。同事會(huì)結(jié)合redis的哈希表實(shí)現(xiàn)议薪。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末尤蛮,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子斯议,更是在濱河造成了極大的恐慌产捞,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哼御,死亡現(xiàn)場(chǎng)離奇詭異坯临,居然都是意外死亡焊唬,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門看靠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赶促,“玉大人,你說我怎么就攤上這事挟炬∨副酰” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵辟宗,是天一觀的道長(zhǎng)爵赵。 經(jīng)常有香客問我吝秕,道長(zhǎng)泊脐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任烁峭,我火速辦了婚禮容客,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘约郁。我一直安慰自己缩挑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布鬓梅。 她就那樣靜靜地躺著供置,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绽快。 梳的紋絲不亂的頭發(fā)上芥丧,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音坊罢,去河邊找鬼续担。 笑死,一個(gè)胖子當(dāng)著我的面吹牛活孩,可吹牛的內(nèi)容都是我干的物遇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼憾儒,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼询兴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起起趾,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤诗舰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后阳掐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體始衅,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冷蚂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了汛闸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝙茶。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖诸老,靈堂內(nèi)的尸體忽然破棺而出隆夯,到底是詐尸還是另有隱情,我是刑警寧澤别伏,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布蹄衷,位于F島的核電站,受9級(jí)特大地震影響厘肮,放射性物質(zhì)發(fā)生泄漏愧口。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一类茂、第九天 我趴在偏房一處隱蔽的房頂上張望耍属。 院中可真熱鬧,春花似錦巩检、人聲如沸厚骗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽领舰。三九已至,卻和暖如春迟螺,著一層夾襖步出監(jiān)牢的瞬間冲秽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工煮仇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留劳跃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓浙垫,卻偏偏與公主長(zhǎng)得像刨仑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子夹姥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

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