HashMap源碼分析

?????? 最近一直特別忙,好不容易閑下來(lái)了秧荆。準(zhǔn)備把HashMap的知識(shí)總結(jié)一下该镣,很久以前看過(guò)HashMap源碼祠墅。一直想把集合類的知識(shí)都總結(jié)一下侮穿,加深自己的基礎(chǔ)。我覺(jué)的java的集合類特別重要毁嗦,能夠深刻理解和應(yīng)用這些集合類能夠讓自己寫的程序上一步臺(tái)階亲茅。

??????? 本文主要根據(jù)自己學(xué)習(xí)與使用HashMap來(lái)解析HashMap的源碼,深入到HashMap的內(nèi)部結(jié)構(gòu)和實(shí)現(xiàn)狗准,增強(qiáng)自己的基礎(chǔ)知識(shí)克锣。同時(shí)會(huì)借鑒網(wǎng)上的相關(guān)資料,深入理解HashMap.

HashMap的內(nèi)部存儲(chǔ)結(jié)構(gòu)

??????? 一提到HashMap我們就知道鍵值對(duì)腔长,即一個(gè)鍵對(duì)應(yīng)一個(gè)值袭祟。可能我們經(jīng)常會(huì)使用HashMap,但是并不關(guān)注里面的內(nèi)部實(shí)現(xiàn)捞附。今天我們就來(lái)學(xué)習(xí)一下HashMap的內(nèi)部實(shí)現(xiàn)巾乳。

???????? HashMap是一種以鍵值對(duì)存儲(chǔ)數(shù)據(jù)的容器,每個(gè)對(duì)象在java中都會(huì)有一個(gè)hashCode,HashMap正是借每個(gè)對(duì)象的HashCode來(lái)組織鍵值對(duì)的存儲(chǔ)鸟召,因?yàn)镠ashCode的特性胆绊,使得HashMap以非常快速和高效地地根據(jù)鍵值key進(jìn)行數(shù)據(jù)的存取欧募。鍵值對(duì)的具體實(shí)現(xiàn)在HashMap內(nèi)部會(huì)封裝成一個(gè)Entry[] table压状,Entry[] table是鍵值對(duì)的表現(xiàn)形式。下圖為HashMap的存儲(chǔ)結(jié)構(gòu)槽片。HashMap中Value可以相同何缓,但是鍵不可以相同.

?????? 上圖可以看出來(lái)HashMap是數(shù)組+鏈表的存儲(chǔ)結(jié)構(gòu)肢础,數(shù)組的每一個(gè)元素是一個(gè)鏈表的表頭还栓。這樣的結(jié)構(gòu)能夠綜合2個(gè)經(jīng)常使用的數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)碌廓,數(shù)組查找、遍歷快剩盒,鏈表增加谷婆、刪除快。

HashMap的屬性

?????? static final int DEFAULT_INITIAL_CAPACITY = 16;//默認(rèn)初始化加載容量辽聊,即table數(shù)組的長(zhǎng)度纪挎。

?????? static final int MAXIMUM_CAPACITY = 1 << 30;//最大的容量。1左移30位跟匆,2的30次方:1073741824

?????? static final float DEFAULT_LOAD_FACTOR = 0.75f;//默認(rèn)加載因子為0.75

?????? transient Entry[] table;//table數(shù)組异袄,上圖的黃色部分。Entry為藍(lán)色部分

?????? transient int size;//HashMap存儲(chǔ)元素的數(shù)量玛臂。

?????? int threshold;//閥值? table的長(zhǎng)度*加載因子(默認(rèn)wei0.75)

?????? final float loadFactor;//加載因子

?????? transient volatile int modCount;//修改次數(shù)

?????? 注:如果用transient聲明一個(gè)實(shí)例變量烤蜕,當(dāng)對(duì)象存儲(chǔ)時(shí),它的值不需要維持迹冤。換句話來(lái)說(shuō)就? 是讽营,用transient關(guān)鍵字標(biāo)記的成員變量不參與序列化過(guò)程。

????????????? volatile為同步變量泡徙,保證每次都讀取的值是最新的橱鹏。

HashMap的構(gòu)造方法

??????? public HashMap() {//最常用的改造方法

????????????? this.loadFactor = DEFAULT_LOAD_FACTOR;//默認(rèn)加載因子,0.75

????????????? threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);//閥值??? 默認(rèn)的容量*加載英子堪藐。12

????????????? table = new Entry[DEFAULT_INITIAL_CAPACITY];//table數(shù)組的默認(rèn)為16莉兰,容量為16,切記容量不等于HashMap存儲(chǔ)的元素?cái)?shù)量礁竞,容易混淆贮勃。

????????????? init();//初始化方法,里面是個(gè)空

????????? }

??????? 默認(rèn)的構(gòu)造方法開(kāi)辟16個(gè)大小的空間苏章。還有另外一個(gè)構(gòu)造方法我們使用的比較少寂嘉,當(dāng)你覺(jué)的默認(rèn)的HashMap的存儲(chǔ)空間浪費(fèi)時(shí)(容量達(dá)到3/4時(shí)HashMap就會(huì)調(diào)用resize擴(kuò)大為原來(lái)的2倍),可以使用下面一個(gè)枫绅。

?????? public HashMap(int initialCapacity, float loadFactor) {//初始化容量泉孩,加載因子

?????????????? if (initialCapacity < 0)//容量不能小于0

????????????????????? throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);

????????????? if (initialCapacity > MAXIMUM_CAPACITY)//大于允許最大的容量時(shí),設(shè)置未最大值

????????????????????? initialCapacity = MAXIMUM_CAPACITY;

???????????? if (loadFactor <= 0 || Float.isNaN(loadFactor))//加載因子小于大于0或者不是數(shù)字的時(shí)候并淋,報(bào)非法加載因子異常

???????????? throw new IllegalArgumentException("Illegal load factor: " +

???????????? loadFactor);

???????????? // Find a power of 2 >= initialCapacity

??????????? int capacity = 1;

??????????? while (capacity < initialCapacity)//實(shí)際的開(kāi)辟的空間要大于傳入的第一個(gè)參數(shù)的值寓搬。

??????????? capacity <<= 1;//這是一個(gè)重點(diǎn),capacity才是容量县耽。

??????????? this.loadFactor = loadFactor;//加載因子設(shè)置為傳入的值

??????????? threshold = (int)(capacity * loadFactor);//閾值

?????????? table = new Entry[capacity];//數(shù)組

?????????? init();

??????? }

???????? 此外還有2個(gè)構(gòu)造方法基本上很少用到句喷,感興趣的可以自己看看镣典。HashMap最重要的方法是put和get方法,下面我們重點(diǎn)看看這2個(gè)方法唾琼。

HashMap的put方法分析

???????? public V put(K key, V value) {//很熟悉的方法

????????????? if (key == null)//如果key==null兄春,直接設(shè)置空的

???????????????????? return putForNullKey(value);

????????????? int hash = hash(key.hashCode());//獲得hash值

????????????? int i = indexFor(hash, table.length);//根據(jù)hash值找到此鍵值對(duì)應(yīng)該放在數(shù)組的第幾個(gè)位置。

????????????? for (Entry e = table[i]; e != null; e = e.next) {//拿到放置該鍵值對(duì)的鏈表锡溯,

???????????????????? Object k;

????????????? if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//如果原來(lái)的存在赶舆,直接替換值

????????????????????? V oldValue = e.value;

????????????????????? e.value = value;

????????????????????? e.recordAccess(this);

????????????????????? return oldValue;

????????????????? }

?????????? }

?????????? modCount++;

?????????? addEntry(hash, key, value, i);//不存在就新增

????????? return null;

???? }

????????? put方法會(huì)先判斷鍵是不是空,如果為空就調(diào)putForNullKey方法祭饭。方法如下:

????????? private V putForNullKey(V value) {

??????????????? for (Entry e = table[0]; e != null; e = e.next) {//獲得第一個(gè)鏈表芜茵,遍歷查找是否原來(lái)就存儲(chǔ)為null的鍵值對(duì)

???????????????? if (e.key == null) {//如果存在

?????????????????????? V oldValue = e.value;//記錄下老的值

?????????????????????? e.value = value;//把新值設(shè)置進(jìn)去

?????????????????????? e.recordAccess(this);

?????????????????????? return oldValue;//返回老值

?????????????????? }

?????????????? }

???????????????? modCount++;

??????????????? addEntry(0, null, value, 0);//Key?為null,則將Entry放置到第一桶table[0]中

??????????????? return null;

???????? }

????????? 計(jì)算Hash值的方法如下:

?????????? static int hash(int h) {//根據(jù)特定的hashcode?重新計(jì)算hash值

????????????????????? h ^= (h >>> 20) ^ (h >>> 12);

?????????????????????? return h ^ (h >>> 7) ^ (h >>> 4);

????????????? }

??????????? static int indexFor(int h, int length) {//匹配到具體的桶當(dāng)中,

?????????????????????? return h & (length-1);//相當(dāng)于int i = hash %Entry[].length;得到i后倡蝙,就是在Entry數(shù)組中的位置

???????????? }

?????? 整個(gè)put方法的流程如下:

??????? 首先判斷鍵是否為空九串,如果為空在判斷是否已經(jīng)存在鍵為空的鍵值對(duì),存在就更新值寺鸥,不存在就新增猪钮;

??????? 如果鍵不為空,則獲取這個(gè)Key的hashcode值析既,根據(jù)此值確定鍵值對(duì)的存儲(chǔ)位置躬贡;遍歷所在桶中的Entry鏈表,查找其中是否已經(jīng)有了以Key值為Key存儲(chǔ)的Entry對(duì)象眼坏,若已存在拂玻,定位到對(duì)應(yīng)的Entry,其中的Value值更新為新的Value值;返回舊值宰译;若不存在檐蚜,則根據(jù)鍵值對(duì) 創(chuàng)建一個(gè)新的Entry對(duì)象,然后添加到這個(gè)桶的Entry鏈表的頭部沿侈。當(dāng)前的HashMap的大小(即Entry節(jié)點(diǎn)的數(shù)目)是否超過(guò)了閥值闯第,若超過(guò)了閥值(threshold),則增大HashMap的容量(即Entry[] table 的大小)缀拭,并且重新組織內(nèi)部各個(gè)Entry排列咳短。

HashMap的get方法分析

???????? public V get(Object key) {

????????????? if (key == null)//如果鍵等于null,調(diào)用getForNullKey

???????????????????? return getForNullKey();

???????????? int hash = hash(key.hashCode());//獲得hash值蛛淋,

???????????? for (Entry e = table[indexFor(hash, table.length)];

??????????????????? e != null;

???????????? e = e.next) {//indexfor計(jì)算存儲(chǔ)位置咙好,根據(jù)位置遍歷鏈表

??????????????????? Object k;

??????????? if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

??????????????????? return e.value;//拿到對(duì)應(yīng)的值

???????????? }

?????????????????? return null;

?????? }

???????? 如果鍵等于null,調(diào)用getForNullKey

???????? private V getForNullKey() {

???????????????? for (Entry e = table[0]; e != null; e = e.next) {//鍵為null的鍵值對(duì)存儲(chǔ)在數(shù)組的第一個(gè)元素褐荷,

???????????????? if (e.key == null)//如果存在則返回值

???????????????????????? return e.value;

??????????? }

??????????????????????? return null;//不存在返回null

}

?????? get方法比較簡(jiǎn)單勾效,比較容易理解。主要流程如下:

??????? 首先判斷鍵是否為空,如果為空在table[0]中取鍵為空的鍵值對(duì)层宫,如果不存在為空的則返回null杨伙。

??????? 如果鍵不為空,則獲取這個(gè)Key的hashcode值萌腿,根據(jù)此值確定該鍵值對(duì)的存儲(chǔ)位置限匣;遍歷所在桶中的Entry鏈表,查找其中是否已經(jīng)有了以Key值為Key存儲(chǔ)的Entry對(duì)象哮奇,若已存在膛腐,定位到對(duì)應(yīng)的Entry,獲得Value值睛约;返回舊值鼎俘;若不存在,則返回空辩涝。

HashMap的總結(jié)

??????? 本文主要講了HashMap的存儲(chǔ)結(jié)構(gòu)以及基本屬性贸伐、構(gòu)造方法,同時(shí)分析了比較常用的put和get方法怔揩。其他方法請(qǐng)讀者自行查看捉邢。在看HashMap的源碼時(shí),我認(rèn)為以下幾個(gè)方面比較重要商膊,能夠理解到以下的幾點(diǎn)伏伐,搞定HashMap基本不成問(wèn)題。

???????? HashMap的存儲(chǔ)結(jié)構(gòu)晕拆,以及為什么要這樣進(jìn)行存儲(chǔ)藐翎。

???????? HashMap的各個(gè)屬性的含義,以及其擴(kuò)容的策略(擴(kuò)容算法)实幕。

???????? Hash值的計(jì)算.確定桶的位置吝镣。

???????? HashMap中的value可以相同,鍵不可以相同昆庇。

???????? HashMap是非線程安全的末贾。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市整吆,隨后出現(xiàn)的幾起案子拱撵,更是在濱河造成了極大的恐慌,老刑警劉巖表蝙,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拴测,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡勇哗,警方通過(guò)查閱死者的電腦和手機(jī)昼扛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人抄谐,你說(shuō)我怎么就攤上這事渺鹦。” “怎么了蛹含?”我有些...
    開(kāi)封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵毅厚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我浦箱,道長(zhǎng)吸耿,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任酷窥,我火速辦了婚禮咽安,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蓬推。我一直安慰自己刻剥,他們只是感情好甘苍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般揍诽。 火紅的嫁衣襯著肌膚如雪郑气。 梳的紋絲不亂的頭發(fā)上柠并,一...
    開(kāi)封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天弛饭,我揣著相機(jī)與錄音,去河邊找鬼姆另。 笑死喇肋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜕青。 我是一名探鬼主播苟蹈,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼右核!你這毒婦竟也來(lái)了慧脱?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贺喝,失蹤者是張志新(化名)和其女友劉穎菱鸥,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體躏鱼,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡氮采,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了染苛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹊漠。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡主到,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出躯概,到底是詐尸還是另有隱情登钥,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布娶靡,位于F島的核電站牧牢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏姿锭。R本人自食惡果不足惜塔鳍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呻此。 院中可真熱鬧轮纫,春花似錦、人聲如沸趾诗。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)恃泪。三九已至,卻和暖如春犀斋,著一層夾襖步出監(jiān)牢的瞬間贝乎,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工叽粹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留览效,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓虫几,卻偏偏與公主長(zhǎng)得像锤灿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辆脸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • HashMap 是 Java 面試必考的知識(shí)點(diǎn)但校,面試官?gòu)倪@個(gè)小知識(shí)點(diǎn)就可以了解我們對(duì) Java 基礎(chǔ)的掌握程度。網(wǎng)...
    野狗子嗷嗷嗷閱讀 6,657評(píng)論 9 107
  • 一、基本數(shù)據(jù)類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數(shù)值 對(duì)于byte類型而言...
    龍貓小爺閱讀 4,254評(píng)論 0 16
  • HashMap<K,V> Entry<K,V> table = new Entry<K,V>(expectedNu...
    _挑燈看劍_閱讀 224評(píng)論 0 0
  • 5.1倘是、對(duì)于HashMap需要掌握以下幾點(diǎn) Map的創(chuàng)建:HashMap() 往Map中添加鍵值對(duì):即put(Ob...
    rochuan閱讀 666評(píng)論 0 0
  • 荒蕪中亭枷,出現(xiàn)一幢略顯破舊的小樓,復(fù)古搀崭,精致叨粘,在正中上方,圓圓的一個(gè)“旅”字。 別無(wú)去處升敲,那就進(jìn)去吧袍镀。 小旅館的樣子...
    凌空于夢(mèng)閱讀 419評(píng)論 0 2