HASHMAP、HASHTABLE温峭、CONCURRENTHASHMAP的原理與區(qū)別

擴展閱讀HashMap在jdk1.7和1.8中的實現(xiàn)

希望各位小伙伴能帶著如下幾個問題來進行閱讀猛铅,這樣收獲會更大。

HashTable凤藏、HashMap奸忽、ConcurrentHashMap的區(qū)別?

HashMap線程不安全的出現(xiàn)場景揖庄?

HashMap put方法存放數(shù)據(jù)時是怎么判斷是否重復(fù)的栗菜???

JDK7和JDK8?中HashMap的實現(xiàn)有什么區(qū)別?


下面先說這三個Map的區(qū)別:

HashTable

底層數(shù)組+鏈表實現(xiàn)蹄梢,無論key還是value都不能為null疙筹,線程安全,實現(xiàn)線程安全的方式是在修改數(shù)據(jù)時鎖住整個HashTable禁炒,效率低而咆,ConcurrentHashMap做了相關(guān)優(yōu)化

初始size為11,擴容:newsize = olesize*2+1

計算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

HashMap

底層數(shù)組+鏈表實現(xiàn)幕袱,可以存儲null鍵和null值暴备,線程不安全

初始size為16,擴容:newsize = oldsize*2们豌,size一定為2的n次冪

擴容針對整個Map涯捻,每次擴容時,原來數(shù)組中的元素依次重新計算存放位置望迎,并重新插入

插入元素后才判斷該不該擴容障癌,有可能無效擴容(插入后如果擴容,如果沒有再次插入辩尊,就會產(chǎn)生無效擴容)

當(dāng)Map中元素總數(shù)超過Entry數(shù)組的75%涛浙,觸發(fā)擴容操作,為了減少鏈表長度,元素分配更均勻

計算index方法:index = hash & (tab.length – 1)

HashMap的初始值還要考慮加載因子:哈希沖突:若干Key的哈希值按數(shù)組大小取模后蝗拿,如果落在同一個數(shù)組下標(biāo)上晾捏,將組成一條Entry鏈,對Key的查找需要遍歷Entry鏈上的每個元素執(zhí)行equals()比較哀托。加載因子:為了降低哈希沖突的概率,默認(rèn)當(dāng)HashMap中的鍵值對達到數(shù)組大小的75%時劳秋,即會觸發(fā)擴容仓手。因此,如果預(yù)估容量是100玻淑,即需要設(shè)定100/0.75=134的數(shù)組大小嗽冒》褪剑空間換時間:如果希望加快Key查找的時間蛉威,還可以進一步降低加載因子,加大初始大小雇锡,以降低哈希沖突的概率箫锤。

HashMap的內(nèi)部結(jié)構(gòu)可以看作是數(shù)組(Node[] table)和鏈表的復(fù)合結(jié)構(gòu)贬蛙,數(shù)組被分為一個個桶(bucket),通過哈希值決定了鍵值對在這個數(shù)組中的尋址(哈希值相同的鍵值對谚攒,則以鏈表形式存儲)阳准,如下圖所示。有一點需要注意馏臭,如果鏈表大小超過閾值(TREEIFY_THRESHOLD,8)野蝇,圖中的鏈表就會被改造為樹形結(jié)構(gòu)


HashMap和Hashtable都是用hash算法來決定其元素的存儲括儒,因此HashMap和Hashtable的hash表包含如下屬性:

容量(capacity):hash表中桶的數(shù)量

初始化容量(initial capacity):創(chuàng)建hash表時桶的數(shù)量绕沈,HashMap允許在構(gòu)造器中指定初始化容量

尺寸(size):當(dāng)前hash表中記錄的數(shù)量

負(fù)載因子(load factor):負(fù)載因子等于“size/capacity”。負(fù)載因子為0帮寻,表示空的hash表乍狐,0.5表示半滿的散列表,依此類推规婆。輕負(fù)載的散列表具有沖突少澜躺、適宜插入與查詢的特點(但是使用Iterator迭代元素時比較慢)

除此之外,hash表里還有一個“負(fù)載極限”抒蚜,“負(fù)載極限”是一個0~1的數(shù)值掘鄙,“負(fù)載極限”決定了hash表的最大填滿程度。當(dāng)hash表中的負(fù)載因子達到指定的“負(fù)載極限”時嗡髓,hash表會自動成倍地增加容量(桶的數(shù)量)操漠,并將原有的對象重新分配,放入新的桶內(nèi),這稱為rehashing浊伙。

HashMap和Hashtable的構(gòu)造器允許指定一個負(fù)載極限撞秋,HashMap和Hashtable默認(rèn)的“負(fù)載極限”為0.75,這表明當(dāng)該hash表的3/4已經(jīng)被填滿時嚣鄙,hash表會發(fā)生rehashing吻贿。

“負(fù)載極限”的默認(rèn)值(0.75)是時間和空間成本上的一種折中:

較高的“負(fù)載極限”可以降低hash表所占用的內(nèi)存空間,但會增加查詢數(shù)據(jù)的時間開銷哑子,而查詢是最頻繁的操作(HashMap的get()與put()方法都要用到查詢)

較低的“負(fù)載極限”會提高查詢數(shù)據(jù)的性能舅列,但會增加hash表所占用的內(nèi)存開銷

程序猿可以根據(jù)實際情況來調(diào)整“負(fù)載極限”值,但一般不建議輕易修改卧蜓,因為JDK自身的默認(rèn)負(fù)載因子是非常符合通用場景需求的帐要。如果確實需要修改,建議不要設(shè)置超過0.75弥奸,因為會顯著增加沖突榨惠,降低HashMap的性能。

根據(jù)容量和負(fù)載因子的關(guān)系盛霎,我們可以預(yù)先設(shè)置合適的容量大小赠橙,具體數(shù)值我們可以根據(jù)擴容發(fā)生的條件來做簡單預(yù)估,計算公式如下:

負(fù)載因子 * 容量 > 元素數(shù)量

所以預(yù)先設(shè)置的容量需要大于“預(yù)估元素數(shù)量/負(fù)載因子”摩渺,同時它是2的冪數(shù)简烤。

上面提到HashMap會樹化,為什么會這樣呢摇幻?

本質(zhì)上這是個安全問題横侦。因為在元素放置過程中,如果一個對象哈希沖突绰姻,都被放置到同一個桶里枉侧,則會形成一個鏈表,我們知道鏈表查詢是線性的狂芋,會嚴(yán)重影響存取的性能榨馁。而在現(xiàn)實世界,構(gòu)造哈希沖突的數(shù)據(jù)并不是非常復(fù)雜的事情帜矾,惡意代碼就可以利用這些數(shù)據(jù)大量與服務(wù)器端交互翼虫,導(dǎo)致服務(wù)器端CPU大量占用,這就構(gòu)成了哈希碰撞拒絕服務(wù)攻擊屡萤,國內(nèi)一線互聯(lián)網(wǎng)公司就發(fā)生過類似攻擊事件珍剑。

ConcurrentHashMap

底層采用分段的數(shù)組+鏈表實現(xiàn),線程安全

通過把整個Map分為N個Segment死陆,可以提供相同的線程安全招拙,但是效率提升N倍,默認(rèn)提升16倍。(讀操作不加鎖别凤,由于HashEntry的value變量是 volatile的饰序,也能保證讀取到最新的值。)

Hashtable的synchronized是針對整張Hash表的规哪,即每次鎖住整張表讓線程獨占求豫,ConcurrentHashMap允許多個修改操作并發(fā)進行,其關(guān)鍵在于使用了鎖分離技術(shù)

有些方法需要跨段由缆,比如size()和containsValue()注祖,它們可能需要鎖定整個表而而不僅僅是某個段,這需要按順序鎖定所有段均唉,操作完畢后,又按順序釋放所有段的鎖

擴容:段內(nèi)擴容(段內(nèi)元素超過該段對應(yīng)Entry數(shù)組長度的75%觸發(fā)擴容肚菠,不會對整個Map進行擴容)舔箭,插入前檢測需不需要擴容,有效避免無效擴容

????????Hashtable和HashMap都實現(xiàn)了Map接口蚊逢,但是Hashtable的實現(xiàn)是基于Dictionary抽象類的层扶。Java5提供了ConcurrentHashMap,它是HashTable的替代烙荷,比HashTable的擴展性更好镜会。

HashMap基于哈希思想,實現(xiàn)對數(shù)據(jù)的讀寫终抽。當(dāng)我們將鍵值對傳遞給put()方法時戳表,它調(diào)用鍵對象的hashCode()方法來計算hashcode,然后找到bucket位置來存儲值對象昼伴。當(dāng)獲取對象時匾旭,通過鍵對象的equals()方法找到正確的鍵值對,然后返回值對象圃郊。HashMap使用鏈表來解決碰撞問題价涝,當(dāng)發(fā)生碰撞時,對象將會儲存在鏈表的下一個節(jié)點中持舆。HashMap在每個鏈表節(jié)點中儲存鍵值對對象色瘩。當(dāng)兩個不同的鍵對象的hashcode相同時,它們會儲存在同一個bucket位置的鏈表中逸寓,可通過鍵對象的equals()方法來找到鍵值對居兆。如果鏈表大小超過閾值(TREEIFY_THRESHOLD,8),鏈表就會被改造為樹形結(jié)構(gòu)席覆。

在HashMap中史辙,null可以作為鍵,這樣的鍵只有一個,但可以有一個或多個鍵所對應(yīng)的值為null聊倔。當(dāng)get()方法返回null值時晦毙,即可以表示HashMap中沒有該key,也可以表示該key所對應(yīng)的value為null耙蔑。因此见妒,在HashMap中不能由get()方法來判斷HashMap中是否存在某個key,應(yīng)該用containsKey()方法來判斷甸陌。而在Hashtable中须揣,無論是key還是value都不能為null。

??????? Hashtable是線程安全的钱豁,它的方法是同步的耻卡,可以直接用在多線程環(huán)境中。而HashMap則不是線程安全的牲尺,在多線程環(huán)境中卵酪,需要手動實現(xiàn)同步機制。

Hashtable與HashMap另一個區(qū)別是HashMap的迭代器(Iterator)是fail-fast迭代器谤碳,而Hashtable的enumerator迭代器不是fail-fast的溃卡。所以當(dāng)有其它線程改變了HashMap的結(jié)構(gòu)(增加或者移除元素),將會拋出ConcurrentModificationException蜒简,但迭代器本身的remove()方法移除元素則不會拋出ConcurrentModificationException異常瘸羡。但這并不是一個一定發(fā)生的行為,要看JVM搓茬。

先看一下簡單的類圖:

????????從類圖中可以看出來在存儲結(jié)構(gòu)中ConcurrentHashMap比HashMap多出了一個類Segment犹赖,而Segment是一個可重入鎖。

????????ConcurrentHashMap是使用了鎖分段技術(shù)來保證線程安全的垮兑。

鎖分段技術(shù):首先將數(shù)據(jù)分成一段一段的存儲冷尉,然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個線程占用鎖訪問其中一個段數(shù)據(jù)的時候系枪,其他段的數(shù)據(jù)也能被其他線程訪問雀哨。?

ConcurrentHashMap提供了與Hashtable和SynchronizedMap不同的鎖機制。Hashtable中采用的鎖機制是一次鎖住整個hash表私爷,從而在同一時刻只能由一個線程對其進行操作雾棺;而ConcurrentHashMap中則是一次鎖住一個桶。

ConcurrentHashMap默認(rèn)將hash表分為16個桶衬浑,諸如get捌浩、put、remove等常用操作只鎖住當(dāng)前需要用到的桶工秩。這樣尸饺,原來只能一個線程進入进统,現(xiàn)在卻能同時有16個寫線程執(zhí)行,并發(fā)性能的提升是顯而易見的浪听。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末螟碎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子迹栓,更是在濱河造成了極大的恐慌掉分,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件克伊,死亡現(xiàn)場離奇詭異酥郭,居然都是意外死亡,警方通過查閱死者的電腦和手機愿吹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門不从,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人犁跪,你說我怎么就攤上這事消返。” “怎么了耘拇?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宇攻。 經(jīng)常有香客問我惫叛,道長,這世上最難降的妖魔是什么逞刷? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任嘉涌,我火速辦了婚禮,結(jié)果婚禮上夸浅,老公的妹妹穿的比我還像新娘仑最。我一直安慰自己,他們只是感情好帆喇,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布警医。 她就那樣靜靜地躺著,像睡著了一般坯钦。 火紅的嫁衣襯著肌膚如雪预皇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天婉刀,我揣著相機與錄音吟温,去河邊找鬼。 笑死突颊,一個胖子當(dāng)著我的面吹牛鲁豪,可吹牛的內(nèi)容都是我干的潘悼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼爬橡,長吁一口氣:“原來是場噩夢啊……” “哼治唤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起堤尾,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤肝劲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后郭宝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辞槐,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年粘室,在試婚紗的時候發(fā)現(xiàn)自己被綠了榄檬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡衔统,死狀恐怖鹿榜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锦爵,我是刑警寧澤舱殿,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站险掀,受9級特大地震影響沪袭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜樟氢,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一冈绊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧埠啃,春花似錦死宣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至叹螟,卻和暖如春鹃骂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背罢绽。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工畏线, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人良价。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓寝殴,卻偏偏與公主長得像蒿叠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蚣常,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

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