Java7、8中HashMap和ConcurrentHashMap原始碼閱讀
阿新 • • 發佈:2018-11-02
首先來看下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) {
inflateTable(threshold);
}
//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沒太讀明白)