hashmap做爲java中很是重要的數據結構,對於key-value類型的存儲(緩存,臨時映射表,。。。)等不可或缺,hashmap自己是非線程安全的,對於多線程條件下須要作競爭條件處理,能夠經過Collections和ConcurrentHashmap來替代。 java
hashmap存儲數據主要是經過數組+鏈表實現的,經過將key的hashcode映射到數組的不一樣元素(桶,hash中的叫法),而後衝突的元素放入鏈表中。 算法
鏈表結構(Entry) 數組
採用靜態內部類 緩存
當存入的值爲null的時候,操做會找到table[0],set key爲null的值爲新值 安全
若果不是空值,則進行hash, 數據結構
能夠看到,算法進行了二次hash,使高位也參與到計算中,防止低位不變形成的hash衝突 多線程
注:這一切的目的實際上都是爲了使value儘可能分佈到不一樣的 性能
對於任意給定的對象,只要它的 hashCode() 返回值相同,那麼程序調用 hash(int h) 方法所計算獲得的 hash 碼值老是相同的。咱們首先想到的就是把 hash 值對數組長度取模運算,這樣一來,元素的分佈相對來講是比較均勻的。可是,「模」運算的消耗仍是比較大的,在 HashMap 中是這樣作的:調用 indexFor(int h, int length) 方法來計算該對象應該保存在 table 數組的哪一個索引處。indexFor(int h, int length) 方法的代碼以下: 優化
這又要牽扯到hashmap的另一個問題,關於length長度的定義 spa
在put操做的開始,有判斷table是否爲空,若是爲空則會初始化table,初始化的代碼以下:
1
2
3
4
5
6
7
|
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
|
容量的提高都是以2的冪的方式
這段代碼保證初始化時 HashMap 的容量老是 2 的 n 次方,即底層數組的長度老是爲 2的 n 次方。當 length 老是 2 的 n 次方時,h& (length-1)運算等價於對 length 取模,也就是h%length,可是&比%具備更高的效率。
這看上去很簡單,其實比較有玄機的,咱們舉個例子來講明:
假設數組長度分別爲 15 和 16,優化後的 hash 碼分別爲 8 和 9,那麼&運算後的結果以下:
讀操做經過hash後的值,同樣是調用indexFor方法找到對應的序號,而後遍歷鏈表找到對應的value返回
resize只有在hashmap中元素的大小達到臨界值的時候纔會進行,而臨界值和loadFactor 參數有關,只有數量達到loadFactor *table.length纔會從新分配table,元素也將從新映射,這是很是耗性能的操做,因此最好一開始能肯定元素的大概範圍
HashMap 的性能參數(這段直接從參考文章引來):
HashMap 包含以下幾個構造器:
HashMap():構建一個初始容量爲 16,負載因子爲 0.75 的 HashMap。
HashMap(int initialCapacity):構建一個初始容量爲 initialCapacity,負載因子
爲 0.75 的 HashMap。
HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的負載因子建立一個 HashMap。HashMap 的基礎構造器 HashMap(int initialCapacity, float loadFactor)帶有兩個參數,它們是初始容量 initialCapacity 和加載因子 loadFactor。
initialCapacity:HashMap 的最大容量,即爲底層數組的長度。
loadFactor:負載因子 loadFactor 定義爲:散列表的實際元素數目(n)/ 散列表的容量(m)。負載因子衡量的是一個散列表的空間的使用程度,負載因子越大表示散列表的裝填程度越高,反之愈小。對於使用鏈表法的散列表來講,查找一個元素的平均時間是 O(1+a),所以若是負載因子越大,對空間的利用更充分,然然後果是查找效率的下降;若是負載因子過小,那麼散列表的數據將過於稀疏,對空間形成嚴重浪費。
HashMap 的實現中,經過 threshold 字段來判斷 HashMap 的最大容量:
Java 代碼
1
2
|
threshold = (int)(capacity * loadFactor);
|
結合負載因子的定義公式可知, threshold 就是在此 loadFactor 和 capacity 對應下容許的最大元素數目,超過這個數目就從新 resize,以下降實際的負載因子。默認的的負載因子0.75 是對空間和時間效率的一個平衡選擇。當容量超出此最大容量時, resize 後的 HashMap容量是容量的兩倍:
if (size++ >= threshold)
resize(2 * table.length);
咱們知道 java.util.HashMap 不是線程安全的,所以若是在使用迭代器的過程當中有其餘
線程修改了 map,那麼將拋出 ConcurrentModificationException,這就是所謂 fail-fast 策略。
這一策略在源碼中的實現是經過 modCount 域, modCount 顧名思義就是修改次數,對HashMap 內容的修改都將增長這個值,那麼在迭代器初始化過程當中會將這個值賦給迭代器的 expectedModCount。