HashMap詳解

問題導(dǎo)入:

?HashMap底層數(shù)據(jù)結(jié)構(gòu)塞淹,如何處理hash沖突位喂,為何HashMap的大小要設(shè)置為2的n次冪挠阁,為什么IndexFor方法里,需要hash&length-1硫朦,為什么HashMap允許null值贷腕,resize()過程,多線程下resize為什么會出現(xiàn)死循環(huán)咬展?

1. HashMap的底層數(shù)據(jù)結(jié)構(gòu)

? ? ? ? ?Hash采用的是鏈地址法(數(shù)組+鏈表)解決hash沖突問題(無外乎在取值和存值的時候泽裳,key會不會沖突),其中在hashMap內(nèi)部定義了一個靜態(tài)的內(nèi)部類Entry破婆,Entry包含key,value涮总,next。

2.初始化HashMap

? ? 分為兩種情況祷舀,利用a.自定義的初始化的容量瀑梗,b.利用HashMap內(nèi)部定義的長度DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16默認(rèn)是16,利用默認(rèn)的長度就是創(chuàng)建一個Entry[] table = new Entry[DEFAULT_INITIAL_CAPACITY?];我們重點講解的是第一種情況蔑鹦,隨機(jī)定義一個初始化的容量夺克,

public?HashMap(int?initialCapacity,?float?loadFactor)?{

????????int?capacity?=?1;//默認(rèn)都是從1開始,位移嚎朽,達(dá)到所有的容量都是2的n次冪

????????while?(capacity?<?initialCapacity)

????????????capacity?<<=?1;//左移1位

????????this.loadFactor?=?loadFactor;

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

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

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

????}

注意table初始大小并不是構(gòu)造函數(shù)中的initialCapacityF膛Α!

而是 >= initialCapacity的2的n次冪

引入新的問題:為什么這么設(shè)計數(shù)組的長度2的n次冪:其實為了更好的解決hash沖突

3. HashMap的存取

? 從? get(key)哟忍, put(key,value)入手狡门,這兩個容易發(fā)生hash沖突的地方,沖突主要是key的定位

存儲時:

?int?hash?=?hash(key.hashCode());//先計算key的hashCode锅很,然后再hash

?int?i?=?indexFor(hash,?table.length);//定位到具體的table數(shù)組中

取值時:

?int?hash?=?hash(key.hashCode());//先計算key的hashCode其馏,然后再hash

?int?i?=?indexFor(hash,?table.length);//定位到具體的table數(shù)組中

?Entry[i];

3.1 put(key,value)的詳解

? ? ? 當(dāng)存入值的時候,先計算key的hashCode,然后再hash爆安,然后通過indexFor()定位到具體的table數(shù)組中叛复,這時候出現(xiàn)當(dāng)多個key定位到數(shù)組的同一個位置,先遍歷該位置上的鏈表,判斷有沒有key相同,如果有的話褐奥,更新對應(yīng)key的value,如果沒有插入到頭節(jié)點

public?V?put(K?key,?V?value)?{

????????if?(key?==?null)

? ? ? returnputForNullKey(value);//放入null值的時候咖耘,null總是放在數(shù)組的第一個鏈表中

????????int?hash?=?hash(key.hashCode());

????????int?i?=?indexFor(hash,?table.length);//定位table的位置

????????//遍歷鏈表

????????for?(Entry<K,V>?e?=?table[i];?e?!=?null;?e?=?e.next)?{

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

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

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

????????????????e.value?=?value;???????//如果key在鏈表中已存在,則替換為新value

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

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

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

????????}

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

????????addEntry(hash,?key,?value,?i);

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

????}

void?addEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{

????Entry<K,V>?e?=?table[bucketIndex];

????table[bucketIndex]?=?new?Entry<K,V>(hash,?key,?value,?e);?//參數(shù)e, 是Entry.next

????if?(size++?>=?threshold)

????????????resize(2?*?table.length);????//如果size超過threshold撬码,則擴(kuò)充table大小儿倒,再散列

? ? ? ? //先加入值,再擴(kuò)容呜笑,可能導(dǎo)致最后一次擴(kuò)容的浪費(用不著了)

}

3.2 get(key)

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

????????if?(key?==?null)

????????????return?getForNullKey();//當(dāng)key為null的時候

????????int?hash?=?hash(key.hashCode());? //先定位到數(shù)組元素夫否,再遍歷該元素處的鏈表

????????for?(Entry<K,V>?e?=?table[indexFor(hash,?table.length)]; e?!=?null;e?=?e.next)?{

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

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

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

????????}

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

}

3.3 null的存取

null key總是存放在Entry[]數(shù)組的第一個元素。

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

????????for?(Entry<K,V>?e?=?table[0];?e?!=?null;?e?=?e.next)?{

????????????if?(e.key?==?null)?{

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

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

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

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

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

????????}

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

????????addEntry(0,?null,?value,?0);

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

????}

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

????????for?(Entry<K,V>?e?=?table[0];?e?!=?null;?e?=?e.next)?{

????????????if?(e.key?==?null)

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

????????}

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

}

3.4確定table數(shù)組的位置

????static?int?indexFor(int?h,?int?length)?{

????????return?h?&?(length-1);

????}

按位取并叫胁,作用上相當(dāng)于取模mod或者取余%凰慈。

因為hashMap容量的變化,我們總是將容量定位>=initCapacity的2的次冪曹抬,所以length-1,則每個位置上都是1溉瓶,這樣的與運算,可以保證均勻定位

3.5 resize()重新擴(kuò)容

void?resize(int?newCapacity)?{//newCapacity的值是舊的capacity的值的2倍谤民,保證容量一直是2的次冪

????????Entry[]?oldTable?=?table;

????????int?oldCapacity?=?oldTable.length;

????????if?(oldCapacity?==?MAXIMUM_CAPACITY)?{

????????????threshold?=?Integer.MAX_VALUE;

????????????return;

????????}

????????Entry[]?newTable?=?new?Entry[newCapacity];//創(chuàng)建新的table數(shù)組

????????transfer(newTable);//舊的hashMap到新的hashMap值的轉(zhuǎn)移

????????table?=?newTable;

????????threshold?=?(int)(newCapacity?*?loadFactor);

????}


?void?transfer(Entry[]?newTable)?{

????????Entry[]?src?=?table;

????????int?newCapacity?=?newTable.length;

????????for?(int?j?=?0;?j?<?src.length;?j++)?{

????????????Entry<K,V>?e?=?src[j];

????????????if?(e?!=?null)?{

????????????????src[j]?=?null;

????????????????do?{

????????????????????Entry<K,V>?next?=?e.next;

????????????????????int?i?=?indexFor(e.hash,?newCapacity);?//重新計算index

????????????????????e.next?=?newTable[i];

????????????????????newTable[i]?=?e;//頭插法插到鏈表中

????????????????????e?=?next;

????????????????}?while?(e?!=?null);

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

????????}

????}

4.多線程并發(fā)的情況

在并發(fā)的情況下容易出現(xiàn)死循環(huán)

void transfer(Entry[] newTable) {

? ? ? ? Entry[] src = table;

? ? ? ? int newCapacity = newTable.length;

? ? ? ? for (int j = 0; j < src.length; j++) {

? ? ? ? ? ? Entry<K,V> e = src[j];

? ? ? ? ? ? if (e != null) {

? ? ? ? ? ? ? ? src[j] = null;

? ? ? ? ? ? ? ? do {

? ? ? ? ? ? ? ? ? ? Entry<K,V> next = e.next;//如果在線程一執(zhí)行到第9行代碼就被CPU調(diào)度掛起堰酿,去執(zhí)行線程2,且線程2把上面代碼都執(zhí)行完畢

? ? ? ? ? ? ? ? ? ? int i = indexFor(e.hash, newCapacity);

? ? ? ? ? ? ? ? ? ? e.next = newTable[i];

? ? ? ? ? ? ? ? ? ? newTable[i] = e;

? ? ? ? ? ? ? ? ? ? e = next;

? ? ? ? ? ? ? ? } while (e != null);

? ? ? ? ? ? }

? ? ? ? }

? ? }


? ??http://www.reibang.com/p/1e9cf0ac07f4

https://www.cnblogs.com/dongguacai/p/5599100.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末张足,一起剝皮案震驚了整個濱河市触创,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌为牍,老刑警劉巖哼绑,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碉咆,居然都是意外死亡抖韩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門疫铜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茂浮,“玉大人,你說我怎么就攤上這事壳咕∠浚” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵谓厘,是天一觀的道長幌羞。 經(jīng)常有香客問我,道長竟稳,這世上最難降的妖魔是什么属桦? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任熊痴,我火速辦了婚禮,結(jié)果婚禮上地啰,老公的妹妹穿的比我還像新娘愁拭。我一直安慰自己,他們只是感情好亏吝,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盏混,像睡著了一般蔚鸥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上许赃,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天止喷,我揣著相機(jī)與錄音,去河邊找鬼混聊。 笑死弹谁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的句喜。 我是一名探鬼主播预愤,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼咳胃!你這毒婦竟也來了植康?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤展懈,失蹤者是張志新(化名)和其女友劉穎销睁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體存崖,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡冻记,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了来惧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冗栗。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖违寞,靈堂內(nèi)的尸體忽然破棺而出贞瞒,到底是詐尸還是另有隱情,我是刑警寧澤趁曼,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布军浆,位于F島的核電站,受9級特大地震影響挡闰,放射性物質(zhì)發(fā)生泄漏乒融。R本人自食惡果不足惜掰盘,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赞季。 院中可真熱鬧愧捕,春花似錦、人聲如沸申钩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撒遣。三九已至邮偎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間义黎,已是汗流浹背禾进。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留廉涕,地道東北人泻云。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像狐蜕,于是被迫代替她去往敵國和親宠纯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351