HashMap之淺析
-
什麼是HashMap?
HashMap的底層結構實際上是“連結串列雜湊”,即陣列和連結串列的結合體 為什麼用HashMap?
- HashMap是一個雜湊桶(陣列和連結串列),它儲存的內容是鍵值對(key-value)對映
- HashMap採用了陣列和連結串列的資料結構,能在查詢和修改方便繼承了陣列的線性查詢和連結串列的定址修改
- HashMap是非synchronized,所以HashMap很快
- HashMap可以接受null鍵和值,而Hashtable則不能(equlas()方法需要物件,HashMap是後出的API經過處理才可以)
-
HashMap的工作原理
第一,如圖所示,HashMap有3個要素:hash函式+陣列+單連結串列
第二,對於hash函式而言,需要考慮些什麼?
要快,對於給定的Key,要能夠快速計算出在陣列中的index。那麼什麼運算夠快呢?顯然是位運算!
我們使用put(key, value)儲存物件到HashMap中,使用get(key)從HashMap中獲取物件。當我們給put()方法傳遞鍵和值時,我們先對鍵呼叫hashCode()方法,計算並返回的hashCode是用於找到Map陣列的bucket位置來儲存Node 物件。這裡關鍵點在於指出,HashMap是在bucket中儲存鍵物件和值物件,作為Map.Node 。
以下是具體的put過程(JDK1.8版)
1、對Key求Hash值,然後再計算下標
2、如果沒有碰撞,直接放入桶中(碰撞的意思是計算得到的Hash值相同,需要放到同一個bucket中)
3、如果碰撞了,以連結串列的方式連結到後面
4、如果連結串列長度超過閥值( TREEIFY THRESHOLD==8),就把連結串列轉成紅黑樹,連結串列長度低於6,就把紅黑樹轉回連結串列
5、如果節點已經存在就替換舊值
6、如果桶滿了(容量16*載入因子0.75),就需要 resize(擴容2倍後重排)
以下是具體get過程(考慮特殊情況如果兩個鍵的hashcode相同,你如何獲取值物件?)
當我們呼叫get()方法,HashMap會使用鍵物件的hashcode找到bucket位置,找到bucket位置之後,會呼叫keys.equals()方法去找到連結串列中正確的節點,最終找到要找的值物件。
- 有什麼方法可以減少碰撞?
擾動函式可以減少碰撞,原理是如果兩個不相等的物件返回不同的hashcode的話,那麼碰撞的機率就會小些,這就意味著存連結串列結構減小,這樣取值的話就不會頻繁呼叫equal方法,這樣就能提高HashMap的效能。(擾動即Hash方法內部的演算法實現,目的是讓不同物件返回不同hashcode。)
使用不可變的、宣告作final的物件,並且採用合適的equals()和hashCode()方法的話,將會減少碰撞的發生。不可變性使得能夠快取不同鍵的hashcode,這將提高整個獲取物件的速度,使用String,Interger這樣的wrapper類作為鍵是非常好的選擇。為什麼String, Interger這樣的wrapper類適合作為鍵?因為String是final的,而且已經重寫了equals()和hashCode()方法了。不可變性是必要的,因為為了要計算hashCode(),就要防止鍵值改變,如果鍵值在放入時和獲取時返回不同的hashcode的話,那麼就不能從HashMap中找到你想要的物件。
- 對於hash函式而言,需要考慮些什麼?
- 要快,對於給定的Key,要能夠快速計算出在陣列中的index。那麼什麼運算夠快呢?顯然是位運算!
- 要均勻分佈,較少碰撞。說白了,我們希望通過hash函式,讓資料均勻分佈在陣列中,儘量使得每個位置上的元素數量只有一個,不希望大量資料發生碰撞,導致連結串列過長,遍歷連結串列耗時。那麼怎麼辦到呢?也是利用位運算,通過對資料的二進位制的位進行移動,讓hash函式得到的資料雜湊開來,從而減低了碰撞的概率。
我們來看看JDK1.8的原始碼是怎麼做的(被樓主修飾了一下)
static final int hash(Object key) {
if (key == null){
return 0;
}
int h;
h=key.hashCode();返回雜湊值也就是hashcode
// ^ :按位異或
// >>>:無符號右移,忽略符號位,空位都以0補齊
//其中n是陣列的長度,即Map的陣列部分初始化長度
return (n-1)&(h ^ (h >>> 16));
}
簡單來說就是
1、高16bt不變,低16bit和高16bit做了一個異或(得到的HASHCODE轉化為32位的二進位制,前16位和後16位低16bit和高16bit做了一個異或)
2、(n·1)&hash=->得到下標
技術討論 & 疑問建議 & 個人部落格
版權宣告: 本部落格所有文章除特別宣告外,均採用 CC BY-NC-SA 3.0 許可協議,轉載請註明出處!
相關文章
- JavaScript之淺析PromiseJavaScriptPromise
- 淺析Spring之IoCSpring
- 淺析Kubernetes架構之workqueue架構
- Flutter 之 InheritWidget 原始碼淺析Flutter原始碼
- Hadoop叢集之淺析安全模式Hadoop模式
- nginx listen指令淺析之add listenNginx
- JAVA併發之阻塞佇列淺析Java佇列
- Java NIO 系列文章之 淺析Reactor模式JavaReact模式
- IOS學習之淺析深拷貝與淺拷貝iOS
- iOS Block淺淺析iOSBloC
- 淺析RedisRedis
- Jvm 淺析JVM
- CGLib淺析CGLib
- 淺析XMLXML
- MongoDB淺析MongoDB
- 淺析 JWTJWT
- 淺析 DDD
- RunLoop 淺析OOP
- 淺析 ReentrantLockReentrantLock
- Unstated淺析
- 淺析SharedPreferences
- Nginx淺析Nginx
- 淺析PromisePromise
- ejs 淺析JS
- 淺析KubernetesStatefulSet
- AIDL淺析AI
- ArrayList淺析
- JVM 系列文章之 GC 演算法淺析JVMGC演算法
- 淺談Java中的HashmapJavaHashMap
- 服務端模板注入攻擊 (SSTI) 之淺析服務端
- 淺析spring——IOC 之 分析 Bean 的生命週期SpringBean
- .39-淺析webpack原始碼之parser.parseWeb原始碼
- 淺析AIGC for MMKGAIGC
- Seata原理淺析
- 淺析JNDI注入
- 淺析DES原理
- shadow DOM 淺析
- 淺析Promise原理Promise