Java源碼解析之HashMap
阿新 • • 發佈:2017-08-31
git else github isnan src transfer 存儲 做的 log
一、HashMap類聲明:
HashMap繼承於AbstractMap並且實現了接口Map,Cloneable,Serializable。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {}
二、HashMap類層次:
HashMap實現了三個接口,繼承一個抽象類。除此之外我們應該知道Object是所有類的超類。之所以有一個AbstractMap抽象類,是為了提供一個Map接口實現的骨架,並提供一些實現,最少化實現Map的實現類。
關於序列化,克隆接口另寫文章介紹,敬請期待。
三、HashMap存儲結構圖:
圖片來自:http://github.thinkingbar.com/hashmap-analysis/, 敬謝。
四、HashMap變量說明:
//默認的初始化容量,必須是2的的次方數,默認是16;在初始化HashMap沒有指定容量時使用 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //最大容量2^30,用於table數組,下面做的說明大家不要和size混淆 //我們知道int是32位的,最大正整數是2^31-1, //另外我們分析源碼會發現在resize的時候將閾值設為了Integer.MAX_VALUE,即2^31-1//所以HashMap實際用到的最大size為2^31-1 static final int MAXIMUM_CAPACITY = 1 << 30; //默認的裝載因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; //空數組,被所有HashMap共享,一般只是作為table的默認值 static final Entry<?,?>[] EMPTY_TABLE = {}; //用於存儲HashMap中的桶,長度總是2的次方數,會在第一次put的時候分配內存 //transient表明序列化的時候table不會序列化 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//size表示HashMap中存儲的映射個數 transient int size; //HashMap實際使用的閾值變量 //一開始這個值是默認的閾值, //但是當分配table內存是,值為容量*裝載因子,即(capacity * load factor) int threshold; //裝載因子,用以表示table裝滿程度 //當沒有指明裝載因子時使用DEFAULT_LOAD_FACTOR final float loadFactor; //用以記錄HashMap自創建以來結構發生變化的次數 //結構發生變化指:1.增加映射, 2.移除映射, 3.rehash //這個值會使由這個HashMap得到的叠代器(iterators)快速失敗(fail-fast) //因為在生成叠代器的時候復制了一份modCount當時的值,如果在這之後HashMap發生了結構變化, //那麽這個叠代器中的值就不等於modCount,叠代器就拋出ConcurrentModificationException //但是通過這個叠代器去改變HashMap的結構是可以的 transient int modCount; //可選哈希閾值默認值2^31-1 //目的是為了減少String鍵值的弱哈希計算的沖突 //JVM首先讀取系統屬性jdk.map.althashing.threshold, //值為-1時,禁用該值;值為小於0時,拋出異常;值為正則啟用可選哈希 //源碼分析時會進一步講解 static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE; //哈希種子,在分配table存儲時會計算該值 transient int hashSeed = 0; //存儲映射的Set變量 private transient Set<Map.Entry<K,V>> entrySet = null;
五、HashMap方法解析:
1、構造方法:
/** * 通過容量和裝載因子初始化HashMap */ public HashMap(int initialCapacity, float loadFactor) { //以下對容量值進行檢查,確保在(0, MAXIMUM_CAPACITY]之間 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //以下對裝載因子進行檢查,要求是正的Float數字 //思考裝載因子並沒有限制在小於1的範圍內,可見可以是大於1的數字,只是這個時候再也反映不出 //table的裝滿程度,同時put的沖突幾率將增高,裝載因子失去設計時的意義 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; threshold = initialCapacity; //初始閾值為容量值 /** * HashMap中此init方法的方法體是空的,子類有需要可以實現, * 主要是為了執行子類的鉤子,用的是模板方法設計模式 * 這個方法總是在構造方法的最後一步執行,偽構造方法(clone,readObject)也會調用執行 */ init(); / } /** * 使用容量和默認的裝載因子初始化HashMap */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * 使用默認的容量和裝載因子初始化HashMap */ public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); }
2、內部類:
/** * 調用處是在虛擬機啟動好之後 */ private static class Holder { static final int ALTERNATIVE_HASHING_THRESHOLD; static { //獲得系統屬性jdk.map.althashing.threshold //此種方式調用時不進行安全檢查,doPrivileged裏面的代碼享有特權 String altThreshold = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction( "jdk.map.althashing.threshold")); int threshold; try { //當系統屬性值有被設置,那麽獲得該值,否者使用可選哈希閾值默認值 threshold = (null != altThreshold) ? Integer.parseInt(altThreshold) : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT; //如果系統屬性設置為-1,則賦予Integer最大正整數 if (threshold == -1) { threshold = Integer.MAX_VALUE; } //小於0,拋出異常 if (threshold < 0) { throw new IllegalArgumentException("value must be positive integer."); } } catch(IllegalArgumentException failed) { throw new Error("Illegal value for ‘jdk.map.althashing.threshold‘", failed); } //最後存儲為可選哈希值 ALTERNATIVE_HASHING_THRESHOLD = threshold; } } static class Entry<K,V> implements Map.Entry<K,V> { final K key; //鍵值,註意這裏key是final的,說明一旦賦值不允許修改,強調key值設計原則 V value; //映射值 Entry<K,V> next; //關聯的下一個Enrty引用 int hash; //哈希碼 Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } public final int hashCode() { return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); } public final String toString() { return getKey() + "=" + getValue(); } /** * 此方法被調用,當已存在的Entry中的value被修改的時候 * 子類需要實現 */ void recordAccess(HashMap<K,V> m) { } /** * 此方法被調用,當這個Entry被移除 * 子類需要實現 */ void recordRemoval(HashMap<K,V> m) { } } /** * 叠代器實現抽象類,只有next()未實現,留待子類實現 */ private abstract class HashIterator<E> implements Iterator<E> { Entry<K,V> next; // 下一個entry int expectedModCount; // 用於快速失敗機制 int index; // 桶的位置索引 Entry<K,V> current; // 當前entry HashIterator() { expectedModCount = modCount; //賦予當時HashMap的modCount值 if (size > 0) { Entry[] t = table; //找到table數組中按序不為null的那個桶 while (index < t.length && (next = t[index++]) == null) ; } } public final boolean hasNext() { return next != null; } final Entry<K,V> nextEntry() { //這句很重要,當生成叠代器後,HashMap結構發生了變化,則迅速失敗 if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K,V> e = next; if (e == null) throw new NoSuchElementException(); if ((next = e.next) == null) { Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e; } public void remove() { if (current == null) throw new IllegalStateException(); //這句很重要,當生成叠代器後,HashMap結構發生了變化,則迅速失敗 if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; HashMap.this.removeEntryForKey(k); //移除後要更新expectedModCount,否者再次調用會迅速失敗 expectedModCount = modCount; } } private final class ValueIterator extends HashIterator<V> { public V next() { return nextEntry().value; } } private final class KeyIterator extends HashIterator<K> { public K next() { return nextEntry().getKey(); //強調key值設計理念 } } private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() { return nextEntry(); } } /** * 用於生成key的set集合,所有方法都委托給HashMap的方法 */ private final class KeySet extends AbstractSet<K> { public Iterator<K> iterator() { return newKeyIterator(); } public int size() { return size; } public boolean contains(Object o) { return containsKey(o); } public boolean remove(Object o) { return HashMap.this.removeEntryForKey(o) != null; } public void clear() { HashMap.this.clear(); } } /** * 用於生成value集合,所有方法都委托給HashMap的方法 */ private final class Values extends AbstractCollection<V> { public Iterator<V> iterator() { return newValueIterator(); } public int size() { return size; } public boolean contains(Object o) { return containsValue(o); } public void clear() { HashMap.this.clear(); } } /** * 用於生成entry的set集合,所有方法實現都委托給HashMap的方法 */ private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { return newEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<K,V> e = (Map.Entry<K,V>) o; Entry<K,V> candidate = getEntry(e.getKey()); return candidate != null && candidate.equals(e); } public boolean remove(Object o) { return removeMapping(o) != null; } public int size() { return size; } public void clear() { HashMap.this.clear(); } }
3、put方法:
/** * 將鍵值key與映射值value組成一個映射存儲在HashMap中 * 允許key為null,因為key唯一,所以最多有一個這樣的映射 * 允許有多個value為null的映射,但是key不同 */ public V put(K key, V value) { //在第一次put的時候會檢查到table為空並初始化 if (table == EMPTY_TABLE) { inflateTable(threshold); } //如果key為null就調用專用的方法進行put,然後返回 if (key == null) return putForNullKey(value); //根據鍵值key進行哈希計算得到哈希碼 int hash = hash(key); //根據哈希碼計算應當將該映射放在table的哪個索引鏈表下 int i = indexFor(hash, table.length); //遍歷第i個鏈表查找是否存在於key相同的entry,存在則替換entry的value值並返回舊的value值 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))) { V oldValue = e.value; e.value = value; e.recordAccess(this); //put替換存在的entry的value值須調用,子類實現的方法 return oldValue; } } //如果不存在為key的entry,則添加新的entry到HashMap中 //因為HashMap結構發生變化,則modCount加1 //返回null modCount++; addEntry(hash, key, value, i); return null; } /** * put鍵值key為null的時候調用的方法 * 直接在table的第0個索引的鏈表上查找替換或添加 */ private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(0, null, value, 0); return null; } /** * 在table的第bucketIndex個鏈表上添加哈希碼為hash,鍵值為key,映射值為value的entry */ void addEntry(int hash, K key, V value, int bucketIndex) { //首先檢查HashMap的size是否已經到達或者大於閾值,並且第bucketIndex個索引的鏈表不為null //那麽HashMap需要rehash嘗試申請更多table空間,註意是嘗試,不一定能分配到的 //同時我們也能知道如果第bucketIndex個索引的鏈表為null,即時超過閾值也不會去申請空間 //註意size指的是HashMap實際的entry數量,threshold是table的裝滿程度的具體閾值 if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); //嘗試請求申請當前table長度的2倍空間 //重新計算put映射的key的哈希碼 //因為這個方法是給所有key的添加使用的,所以要考慮可以為null的情況 hash = (null != key) ? hash(key) : 0; //重新計算put的映射應該放在申請新空間後的table的哪個索引鏈表上 bucketIndex = indexFor(hash, table.length); } //創建Entry對象,並插入到第bucketIndex個索引鏈表的第一個位置 createEntry(hash, key, value, bucketIndex); } /** * 創建Entry對象,並插入到第bucketIndex個索引鏈表的第一個位置,size加1 */ 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++; }
4、get方法:
/** * 獲取HashMap中鍵值為key的entry的value值 * 如果返回null,有可能存儲的value就是null,也有可能沒有key對應entry,需要結合containsKey檢查 */ public V get(Object key) { if (key == null) return getForNullKey(); //key為null,調用專用方法 Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } private V getForNullKey() { if (size == 0) { return null; } //直接到table的第0個索引鏈表下查找 for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; } final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } //首先根據key計算哈希碼,然後根據哈希碼找到table下的索引鏈表,最後查找 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; }
5、remove方法:
/** * 從HashMap中移除鍵值為key的entry,並且返回對應的value值 */ public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); } final Entry<K,V> removeEntryForKey(Object key) { if (size == 0) { return null; } //首先根據key計算哈希碼,然後根據哈希碼找到table下的索引鏈表 int hash = (key == null) ? 0 : hash(key); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; //查找鏈表 while (e != null) { Entry<K,V> next = e.next; Object k; //找到該key對應的entry if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { //結構該表,計數加1;同時HashMap大小減一 modCount++; size--; //如果移除的是table索引的第一個entry,則直接修改table[i] if (prev == e) table[i] = next; //不是第一個entry,則將移除entry的前一個entry的next指向移除entry的next else prev.next = next; //記錄移除的entry e.recordRemoval(this); return e; } //順序後移,再次循環 prev = e; e = next; } //返回移除的entry return e; }
6、一些公用方法:
/** * 在HashMap使用之前需要做一次膨化處理 */ private void inflateTable(int toSize) { //找到大於等於toSize且是2的次方數的最小數 //所以我們需要知道table數組的長度一定是2的次方數 int capacity = roundUpToPowerOf2(toSize); //計算閾值 //取二者中較小的那個 threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; //初始化哈希種子 initHashSeedAsNeeded(capacity); } private static int roundUpToPowerOf2(int number) { return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; } /** * 初始化hashSeed,但是貌似hashSeed一直是0,不知道其中緣由 */ final boolean initHashSeedAsNeeded(int capacity) { boolean currentAltHashing = hashSeed != 0; boolean useAltHashing = sun.misc.VM.isBooted() && //檢查虛擬機是否已啟動 (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); boolean switching = currentAltHashing ^ useAltHashing; if (switching) { hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) //生成隨機種子 : 0; } return switching; } final int hash(Object k) { int h = hashSeed; //當哈希種子不為0且k是String時調用特殊的哈希算法 if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). // 下面的方法可以使在table每個位置的沖突次數都是一個常數值,在裝載因子為0.75的時候為8 h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; // 因為table的length總是2的次方數,所以下面的方法是對h在length上做模運算 // 比如50 % 16 = 2 = 000010 = 110010 & 001111 return h & (length-1); } /** * resize table的大小 */ void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; //如果容量已經到達最大容量值,就將閾值設為Integer最大值,返回 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; //將當前table內容轉換到新的容量table中 transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } /** * 將舊table中的entry轉化到新table中去,rehash指明是否需要重新計算哈希碼 */ 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; 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; } } }
7、其他方法:略
六、說明:
本文對HashMap的源碼進行了簡略分析。本文基於Java7的JDK1.7.0_79-64分析,由於Java8有改變,之後會基於Java8另寫文章解析。
由於作者文筆拙劣,分析粗鄙,紕漏之處還望各位不吝斧正。
Java源碼解析之HashMap