1. 程式人生 > >Java源碼解析之HashMap

Java源碼解析之HashMap

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