1.HashMap的概念
HashMap 是一個散列表,它存儲的內容是鍵值對(key-value)映射。
HashMap 繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable接口。
HashMap 的實現不是同步的,這意味着它是線程不安全的。它的key、value都可以為null。此外,HashMap中的映射是無序的。
本文重點是介紹HashMap中的“拉鏈法”解決散列沖突。如果想了解其他方面的知識可參考http://www.cnblogs.com/skywang12345/p/3310835.html。
下面先介紹一下散列表及散列沖突的基本概念。
2.Hash表的概念
我們知道,有一種數據結構能夠快速地查找所需的對象(O(1)時間復雜度),這就是散列表(hash table)。散列碼(hash code)是由對象的實例產生的一個整數。
更准確的說,具有不同的數據域的對象將產生不同的散列碼。如下圖(這里順帶提一下散列表的概念,即哈希表的概念):
設所有可能出現的關鍵字集合記為U(簡稱全集)。實際發生(即實際存儲)的關鍵字集合記為K(|K|比|U|小得多)。
散列方法是使用函數h將U映射到表T[0..m-1]的下標上(m=O(|U|))。這樣以U中關鍵字為自變量,以h為函數的運算結果就是相應結點的存儲地址。從而達到在O(1)時間內就可完成查找。
其中:
① h:U→{0,1,2,…,m-1} ,通常稱h為散列函數(Hash Function)。散列函數h的作用是壓縮待處理的下標范圍,使待處理的|U|個值減少到m個值,從而降低空間開銷。
② T為散列表(Hash Table)。
③ h(Ki)(Ki∈U)是關鍵字為Ki結點存儲地址(亦稱散列值或散列地址)。
④ 將結點按其關鍵字的散列地址存儲到散列表中的過程稱為散列(Hashing)
3.散列沖突
兩個不同的關鍵字,由於散列函數值相同,因而被映射到同一表位置上。該現象稱為沖突(Collision)或碰撞。發生沖突的兩個關鍵字稱為該散列函數的同義詞(Synonym)。
上圖中的k2≠k5,但h(k2)=h(k5),故k2和K5所在的結點的存儲地址相同。
有了沖突,那自然要盡可能地去避免沖突。那么如何安全避免沖突呢?有兩個條件:
- 其一是|U|≤m;
- 另一個是設計合適的哈希函數。
這只適用於|U|較小,且關鍵字均事先已知的情況,此時經過精心設計散列函數h有可能完全避免沖突。通常情況下,h是一個壓縮映像。雖然|K|≤m(已知的關鍵字),但|U|>m(未知的關鍵字),故無論怎樣設計h,也不可能完全避免沖突。因此,只能在設計h時盡可能使沖突最少。同時還需要確定解決沖突的方法,使發生沖突的同義詞能夠存儲到表中。
HashMap中采用的“拉鏈法”就是一種沖突解決的方式(hash函數的設計才是沖突避免,但不是一種完全的沖突解決方法),如下圖所示為“拉鏈法”結構。
但是HashMap中的節點是Map.Entry類型的,而不是簡單的value,如下圖所示,左邊是一個Node<K,V>[] table數組(在jdk6中是Entry<K,V>數組),Node是Map.Entry的實現類。
那么它是如何解決沖突的呢?即key值不同的兩個或多個Map.Entry<K,V>可能會插在同一個桶下面,但是當查找到某個特定的hash值的時候,下面掛了很多個<K,V>映射,怎么確定哪個是我要找的那個<K,V>呢?這就是HashMap底層結構的一個亮點,在它的Entry中不僅僅只是插入value的,他是插入整個Entry 的,里面包含key和value的,所以能識別同一個hash值下的不同Map.Entry,想要了解更多,建議查看源碼。