Java7、8中HashMap和ConcurrentHashMap源碼閱讀
阿新 • • 發佈:2018-11-02
動態擴容 nal das pub end flat 數據 算數 ext inflateTable(threshold);
}
首先來看下HashMap的類繼承結構:
public class HashMap extends AbstractMap<K,V> impement Map<K,V>,Coloneable,Serializable{
}
可以看出HashMap實現了Map接口。其裏面的方法都是非線程安全的,且不支持並發操作。
對於HashMap主要看的是get/put方法實現,其在jdk1.7,及1.8在解決哈希沖突的上有所不同。
一、Java7 HashMap
從上面的結構圖中,可以大致看出,HashMap由數組:transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;沒個元素對應為一個單向鏈表,鏈表數據結構如下: static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; }
在HashMap中定義的成員變量:
capacity:當前數組容量,始終保持 2^n,可以擴容,擴容後數組大小為當前的 2 倍。
loadFactor:負載因子,默認為 0.75。
threshold:擴容的閾值,等於 capacity * loadFactor,當容量超過這個值時,數組將擴容。
transient int modCount; //HashMap修改次數,這個值用於和expectedModCount期望修改次數比較。
1、put方法解析:
public V put(K key, V value) {
//1.當插入第一個元素時,需要創建並初始化指定大小的數組
if (table == EMPTY_TABLE) {
}
//2.如果 key 為 null,循環遍歷table[0]上的鏈表,最終會將這個 entry 放到 table[0] 中 if (key == null) return putForNullKey(value); //3.計算key的哈希值 int hash = hash(key); //4、通過h & (length-1)即h%length求模找到鍵值對放在哪個位置。 int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { //循環對應位置的單向鏈表,逐個查找每個鏈表節點上是否有整個鍵了。 Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//hash值為整數,比較性能比equals高;另外短路運算,哈希值系統了就沒必要在比較equals。 V oldValue = e.value;//先將當前節點的鍵對應的值取出來。 e.value = value; //替換為新值。 e.recordAccess(this); return oldValue; } } modCount++; //容器修改次數加1 addEntry(hash, key, value, i); //在指定的位置上添加數據,若空間不夠則動態擴充,當前容量乘以2,新建一個數組,長度為capacity*2;並將原來的數組拷貝過來,更新對應變量。 return null; } 數組初始化: private void inflateTable(int toSize) { // Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize); //指定數組容量,默認為16 threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; //改變數組的引用,指向新創建的數組 initHashSeedAsNeeded(capacity); } 計算鍵值對的位置: static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1); //等同於求模:h%length } 添加節點到鏈表中 void addEntry(int hash, K key, V value, int bucketIndex) { //假如map的元素個數大於等於閾值,並且bucketIndex的位置已經元素,則需要動態擴容 if ((size >= threshold) && (null != table[bucketIndex])) { //擴容 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; //重新計算應該存儲下標 bucketIndex = indexFor(hash, table.length); } //創建元素及鏈表節點 createEntry(hash, key, value, bucketIndex); } //新建一個Entry對象,插入單向鏈表表頭,並增加size(不論是否擴容這一步都要進行) void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; } 數組擴容: void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } //新建一個容量擴充2倍的數組 Entry[] newTable = new Entry[newCapacity]; //調用transfer方法將舊數組中的鍵值對拷貝過來 transfer(newTable, initHashSeedAsNeeded(newCapacity)); //舊數組原來的堆空間設置為引用切斷,指向新數組 table = newTable; //重新計算閾值 threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } 鍵值對移植: /** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; //是否重新計算key的哈希 if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } //重新計算元素位置 int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } } 以上就是保存鍵值對的主要代碼,基本步驟: 1)、計算key的哈希值; 2)、根據哈希值計算數組元素的保存位置(h&(length-1)或h%length); 3)、根據需要擴充數組大小; 4)、將鍵值對插入到對應的鏈表頭部或更新已有值; 2、get方法解析 public V get(Object key) { //如果key為空則直接,在存放元素時是直接存放到table[0],所以直接調用getForNullKey方法遍歷對應鏈表即可。 if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } 遍歷table[0]位置的鏈表,返回對應key==null的值,若果返回null,則有兩種情況,要麽沒有key==null的鍵值對,要麽對應位置上的值為null。 private V getForNullKey() { if (size == 0) { return null; } for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; } key值不為空,則調用返回對應的值: final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; } 總結基本流程: 1、計算鍵的哈希值; 2、根據哈希值找到數組中對於的鏈表; 3、遍歷鏈表,查找對應key的值; 4、在比較查找的過程中,先快速比較哈希值,hash相同則再繼續通過equals比較;
二、java7 ConcurrentHashMap
在java7 下ConcurrentHashMap結構如下:
ConcurrentHashMap是並發版的HashMap,支持復雜的並發操作,通過降低鎖的粒度和cas等實現了高並發,支持原子條件的更新操作,不會拋出ConcurrentModificationException,實現了弱一致性。
ConCurrentHashMap是一個Segment數組,每個segment元素對應一個哈希表(結構類似於HashMap)
未完待續....(ConcurrentHashMap沒太讀明白)
Java7、8中HashMap和ConcurrentHashMap源碼閱讀