日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

HashMap、Hashtable、ConcurrentHashMap的原理与区别

發(fā)布時(shí)間:2025/3/21 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 HashMap、Hashtable、ConcurrentHashMap的原理与区别 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

HashTable
底層數(shù)組+鏈表實(shí)現(xiàn),無論key還是value都不能為null,線程安全,實(shí)現(xiàn)線程安全的方式是在修改數(shù)據(jù)時(shí)鎖住整個(gè)HashTable,效率低,ConcurrentHashMap做了相關(guān)優(yōu)化
初始size為11,擴(kuò)容:newsize = olesize2+1
計(jì)算index的方法:index = (hash & 0x7FFFFFFF) % tab.length
HashMap
底層數(shù)組+鏈表實(shí)現(xiàn),可以存儲(chǔ)null鍵和null值,線程不安全
初始size為16,擴(kuò)容:newsize = oldsize2,size一定為2的n次冪
擴(kuò)容針對(duì)整個(gè)Map,每次擴(kuò)容時(shí),原來數(shù)組中的元素依次重新計(jì)算存放位置,并重新插入
插入元素后才判斷該不該擴(kuò)容,有可能無效擴(kuò)容(插入后如果擴(kuò)容,如果沒有再次插入,就會(huì)產(chǎn)生無效擴(kuò)容)
當(dāng)Map中元素總數(shù)超過Entry數(shù)組的75%,觸發(fā)擴(kuò)容操作,為了減少鏈表長度,元素分配更均勻
計(jì)算index方法:index = hash & (tab.length – 1)

HashMap的初始值還要考慮加載因子:

哈希沖突:若干Key的哈希值按數(shù)組大小取模后,如果落在同一個(gè)數(shù)組下標(biāo)上,將組成一條Entry鏈,對(duì)Key的查找需要遍歷Entry鏈上的每個(gè)元素執(zhí)行equals()比較。
加載因子:為了降低哈希沖突的概率,默認(rèn)當(dāng)HashMap中的鍵值對(duì)達(dá)到數(shù)組大小的75%時(shí),即會(huì)觸發(fā)擴(kuò)容。因此,如果預(yù)估容量是100,即需要設(shè)定100/0.75=134的數(shù)組大小。
空間換時(shí)間:如果希望加快Key查找的時(shí)間,還可以進(jìn)一步降低加載因子,加大初始大小,以降低哈希沖突的概率。
HashMap和Hashtable都是用hash算法來決定其元素的存儲(chǔ),因此HashMap和Hashtable的hash表包含如下屬性:

容量(capacity):hash表中桶的數(shù)量
初始化容量(initial capacity):創(chuàng)建hash表時(shí)桶的數(shù)量,HashMap允許在構(gòu)造器中指定初始化容量
尺寸(size):當(dāng)前hash表中記錄的數(shù)量
負(fù)載因子(load factor):負(fù)載因子等于“size/capacity”。負(fù)載因子為0,表示空的hash表,0.5表示半滿的散列表,依此類推。輕負(fù)載的散列表具有沖突少、適宜插入與查詢的特點(diǎn)(但是使用Iterator迭代元素時(shí)比較慢)
除此之外,hash表里還有一個(gè)“負(fù)載極限”,“負(fù)載極限”是一個(gè)0~1的數(shù)值,“負(fù)載極限”決定了hash表的最大填滿程度。當(dāng)hash表中的負(fù)載因子達(dá)到指定的“負(fù)載極限”時(shí),hash表會(huì)自動(dòng)成倍地增加容量(桶的數(shù)量),并將原有的對(duì)象重新分配,放入新的桶內(nèi),這稱為rehashing。

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

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

較高的“負(fù)載極限”可以降低hash表所占用的內(nèi)存空間,但會(huì)增加查詢數(shù)據(jù)的時(shí)間開銷,而查詢是最頻繁的操作(HashMap的get()與put()方法都要用到查詢)
較低的“負(fù)載極限”會(huì)提高查詢數(shù)據(jù)的性能,但會(huì)增加hash表所占用的內(nèi)存開銷
程序猿可以根據(jù)實(shí)際情況來調(diào)整“負(fù)載極限”值。

ConcurrentHashMap
底層采用分段的數(shù)組+鏈表實(shí)現(xiàn),線程安全
通過把整個(gè)Map分為N個(gè)Segment,可以提供相同的線程安全,但是效率提升N倍,默認(rèn)提升16倍。(讀操作不加鎖,由于HashEntry的value變量是 volatile的,也能保證讀取到最新的值。)
Hashtable的synchronized是針對(duì)整張Hash表的,即每次鎖住整張表讓線程獨(dú)占,ConcurrentHashMap允許多個(gè)修改操作并發(fā)進(jìn)行,其關(guān)鍵在于使用了鎖分離技術(shù)
有些方法需要跨段,比如size()和containsValue(),它們可能需要鎖定整個(gè)表而而不僅僅是某個(gè)段,這需要按順序鎖定所有段,操作完畢后,又按順序釋放所有段的鎖
擴(kuò)容:段內(nèi)擴(kuò)容(段內(nèi)元素超過該段對(duì)應(yīng)Entry數(shù)組長度的75%觸發(fā)擴(kuò)容,不會(huì)對(duì)整個(gè)Map進(jìn)行擴(kuò)容),插入前檢測(cè)需不需要擴(kuò)容,有效避免無效擴(kuò)容

Hashtable和HashMap都實(shí)現(xiàn)了Map接口,但是Hashtable的實(shí)現(xiàn)是基于Dictionary抽象類的。Java5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴(kuò)展性更好。

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

在HashMap中,null可以作為鍵,這樣的鍵只有一個(gè),但可以有一個(gè)或多個(gè)鍵所對(duì)應(yīng)的值為null。當(dāng)get()方法返回null值時(shí),即可以表示HashMap中沒有該key,也可以表示該key所對(duì)應(yīng)的value為null。因此,在HashMap中不能由get()方法來判斷HashMap中是否存在某個(gè)key,應(yīng)該用containsKey()方法來判斷。而在Hashtable中,無論是key還是value都不能為null。

Hashtable是線程安全的,它的方法是同步的,可以直接用在多線程環(huán)境中。而HashMap則不是線程安全的,在多線程環(huán)境中,需要手動(dòng)實(shí)現(xiàn)同步機(jī)制。

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

先看一下簡(jiǎn)單的類圖:


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

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

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

ConcurrentHashMap提供了與Hashtable和SynchronizedMap不同的鎖機(jī)制。Hashtable中采用的鎖機(jī)制是一次鎖住整個(gè)hash表,從而在同一時(shí)刻只能由一個(gè)線程對(duì)其進(jìn)行操作;而ConcurrentHashMap中則是一次鎖住一個(gè)桶。

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

總結(jié)

以上是生活随笔為你收集整理的HashMap、Hashtable、ConcurrentHashMap的原理与区别的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。