Java java.util.HashMap實現原理原始碼分析

jinxueliangcn發表於2014-08-06

一、HashMap原始碼概覽

I.  可以結合JDK原始碼包瞭解java.util.HashMap實現原理

II.  繼承抽象類java.util.AbstractMap,實現了三個介面,分別是:java.util.Map、java.lang.Cloneble、 java.io.Serializable 

III. 主要的成員變數

主要成員變數
編號 變數程式碼 變數註釋
1 static final int DEFAULT_INITIAL_CAPACITY = 16;       

預設的初始化容量大小為16,可以通過HashMap(int initialCapacity)  或者HashMap(int initialCapacity,float loadFactor)這兩個建構函式來改變此容量大小,但是實際的容量值可能與你設定的值不同(具體操作見下面解釋
2 static final int MAXIMUM_CAPACITY = 1 << 30; 最大的容量值,1左移30位,即2³⁰
3 static final float DEFAULT_LOAD_FACTOR = 0.75f; 預設的載入因子(作用見下面解釋
4 transient Entry[] table; Entry陣列, HashMap內部儲存實際就是Entry物件陣列(Entry詳細解釋見下面解釋,關鍵字transient的作用見我另一部落格說明, 點選開啟連結 ). 此陣列如果需要的話,大小可以被重置,但是其長度要一直是2的冪次方
5 transient int size; 此Map的大小(即k-v對映的長度)
6 int threshold; Map準備擴容的臨界閥值(臨界閥值的計算方法見下面的解釋),即在Map在使用過程中,如果發現Map使用量(即keys大小)已經超過此閥值,則會呼叫resize()方法進行重置大小(會進行再雜湊,將舊資料交換到擴容後的Map容器中,詳細步驟見下面解釋
7 final float loadFactor; 載入因子,預設值為此表格中編號3的變數對應的值,即0.75,可以通過構造HashMap(int initialCapacity, float loadFactor) 來改變此值
8 transient volatile int modCount;  

VI.構造方法

i. 預設總共有四個建構函式,歸根到底可以總結只有一個構造,下面將逐一介紹

ii. 構造一:HashMap(int initialCapacity,float loadFactor),原始碼以及分析如下:

    /**
     * 通過給定的容量值和載入因子來構造一個空的 <tt>HashMap</tt>
     * 但是指定的這兩個引數值的範圍是有要求的,具體看下面程式碼段的分析
     *
     * @param  initialCapacity 給定的初始化容量
     * @param  loadFactor      給定的負載因子
     * @throws IllegalArgumentException 如果給定的初始化的容量是負數或者給定的負載因子不是正數
     *         
     */
    public HashMap(int initialCapacity, float loadFactor) {
    	// 如果給定的初始化容量引數是負數,則會丟擲引數不合法的具體異常資訊
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
       
        /*
         * 如果給定的初始化容量大於預設的最大容量值(MAXIMUM_CAPACITY常量見上表格III的編號2變數),
         * 則實際容量給預設的最大容量值
         */
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        
        // 如果負載因子不是正數,則會丟擲引數不合法的具體異常資訊
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

		/*
		 * 計算實際容量 
		 * 1.容量值大小要求是2的冪次方.
		 * 2.所以要找一個數字是2的冪次方,且大於等於給定的容量值,則實際的容量可能會跟你設定的容量值不一樣
		 * 3.理解:則譬如你給定的容量值是14,則實際容量值是 2⁴=16, 如果你給定的容量值是16, 則實際容量值也是 2⁴=16
		 * */
        
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;


        this.loadFactor = loadFactor;
        
		/*
		 * 1.準備擴容的臨界閥值計算:實際的容量與負載因子的乘積. 
		 * 2.理解:譬如你設定實際容量是16,負載因子是0.75,則當HashMap的使用量大小達到16*0.75=12時,
		 *        則需要呼叫resize()方法進行擴容,擴容的詳細過程見下面解釋
		 */  
        threshold = (int)(capacity * loadFactor);
        
        // 初始化Map Entry元素陣列
        table = new Entry[capacity];
        init();
    }



iii.構造二:HashMap(int initialCapacity),原始碼以及分析如下:

    /**
     * 通過給定的容量值和預設的負載因子(0.75)來構造一個空的 <tt>HashMap</tt>
     *
     * @param  initialCapacity 給定的初始容量值
     * @throws IllegalArgumentException 如果給定的初始化的容量是負數
     */
    public HashMap(int initialCapacity) {
    	/*
    	 * 可以看出這個建構函式只是指定了初始的容量值,
    	 * 其實呼叫的是構造一HashMap(int initialCapacity,float loadFactor)這個建構函式,
    	 * 只是負載因子用了預設的0.75(預設的負載因子常量DEFAULT_LOAD_FACTOR)
    	 */
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }



vi. 構造三:HashMap(),原始碼以及分析如下:

    /**
     * 通過預設的初始化容量值(16)以及預設的負載因子(0.75)來構造一個空的 <tt>HashMap</tt>
     */
    public HashMap() {
    	// 預設的負載因子常量
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        // 通過預設的初始容量以及預設的負載因子來計算出預設的負載閥值(16 * 0.75 = 12)
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        // 預設的初始容量值
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }


v. 構造四:HashMap(Map<? extends K, ?extends V> m),原始碼以及分析如下:

    /**
     * 
     * 在給定的Map m基礎上來構造一個新的<tt>HashMap</tt>,其中給定的Map m的對映資料會被拷貝到新的HashMap中。
     * 新構造的HashMap負載因子使用的是預設的0.75,並且其容量值會根據給定的Map m的大小來重新計算容量,
     * 保證新的HashMap容量充足,具體的計算方法參考下面註釋
     *
     * @param   m 給定的基礎Map,資料會被對映到新的HashMap上
     * @throws  NullPointerException 如果給定的Map是null,則會丟擲空指標異常
     */
    public HashMap(Map<? extends K, ? extends V> m) {
    	/*
    	 *1.可以看出對HashMap的初始化構造還是呼叫上面的構造一HashMap(int initialCapacity,float loadFactor)
    	 *這個建構函式,從而對新建的HashMap進行初始化. 
    	 *2.首先是根據指定的Map m的大小來重新計算容量,可以看出新建立的的HashMap的容量大小是取
    	 *	max(m的大小/預設的負載因子(0.75)的商, 預設的初始容量16) 的值作為初始化容量
    	 *3.負載因子使用是預設的0.75
    	 *
    	 */
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        //將給定的Map m的資料拷貝到新建立的HashMap中
        putAllForCreate(m);
    }

vi. 總結:可以看出上述的構造方法,其實歸根到底可以總結為,在建立HashMap時,需要兩個基本的要素:給定合理的容量、給定合理的負載因子.


二、 HashMap實現具體原理分析

   I. 內部儲存如圖:


II. 根據原始碼以及上圖可以分析下述結論

  i. HashMap 內部是以可擴容的陣列(Entry[] table)形式來存數資料,每個陣列元素是Entry的資料結構為單元的連結串列

        ii. 每個Entry 都要具備基本四個屬性:hash值,key值,value值,其在陣列中下標位置index,如圖


   iii. put<K, V>的流程,即新增<K, V>時,HashMap內部的工作流程圖如下:

 



 



相關文章