HashMap 源碼解析1、構造函數

HashMap 源碼解析2、put 相關函數java

類結構

image.png

看到HashMap 的類結構,確定會有個疑問,問什麼HashMap 繼承了AbstractMap 還實現了Map 接口?
我當時也很遺憾,因而我就查了一下,結果是個烏龍。「據java集合框架的創始人Josh Bloch描述,這樣的寫法是一個失誤;最開始寫java集合框架的時候,他認爲這樣寫,在某些地方多是有價值的,直到他意識到錯了。來自於markdown

構造函數

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
    
    /** * 默認的負載因子 */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    int threshold;//擴容伐值
    
    final float loadFactor;//負載因子默認爲 0.75
    
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; 
        // all other fields defaulted
    }
    
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /** * @param initialCapacity 初始容量 * @param loadFactor 負載因子 */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
    
    
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
}
複製代碼

HashMap(int initialCapacity, float loadFactor) 構造函數

前兩個沒什麼好講的,咱們直接看第3個 HashMap(int initialCapacity, float loadFactor)
這裏的重點就是 int tableSizeFor(int cap)函數。框架

tableSizeFor(int cap) 函數

tableSizeFor(int cap) 的做用是返回 大於等於cap 且 最接近cap 的2的冪次方的值函數

static final int MAXIMUM_CAPACITY = 1 << 30;
    /** * Returns a power of two size for the given target capacity. * 返回 大於等於cap 且 最接近cap 的2的冪次方的值 */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
複製代碼

咱們分步驟解析一下這個函數:oop

  1. >>> 無符號右移運算符,高位用0 填充。以下:
a = 1010
a >>> 1 = 0101
複製代碼
  1. | 若是相對應位都是 0,則結果爲 0,不然爲 1。以下:
a     = 1010
b     = 0011
------------
a | b = 1011
複製代碼
  1. |= , a |= b 等同於 a = a|b
這時候咱們就能看懂 `n |= n >>> 1`; 等同於 `n = n | n >>> 1` 
複製代碼
  1. int n = cap - 1; 是由於若是cap不減1,當 cap 原本就是2次冪數值,就會致使返回的結果爲 cap*2 。post

  2. 咱們帶入一個值看下運行結果,假設 cap = 20this

static final int tableSizeFor(int cap) {
        System.out.println("cap = "+ Integer.toBinaryString(cap));
        int n = cap - 1;
        System.out.println("n = "+ Integer.toBinaryString(n));
        n |= n >>> 1;
        System.out.println("n1 = "+ Integer.toBinaryString(n));
        n |= n >>> 2;
        System.out.println("n2 = "+ Integer.toBinaryString(n));
        n |= n >>> 4;
        System.out.println("n4 = "+ Integer.toBinaryString(n));
        n |= n >>> 8;
        System.out.println("n8 = "+ Integer.toBinaryString(n));
        n |= n >>> 16;
        System.out.println("n16 = "+ Integer.toBinaryString(n));
        System.out.println("n+1 = "+ Integer.toBinaryString(n+1));
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
    
    @Test
    public void tableSizeForTest(){
        System.out.println("tableSizeFor:"+ tableSizeFor(20));
    }
複製代碼

我這裏直接把tableSizeFor(int cap) 拷貝出來,而後添加了日誌。結果以下:spa

cap = 10100
n   = 10011
n1  = 11011   (10011 | 01001)
n2  = 11111   (11011 | 01101)
n4  = 11111   (11111 | 01111)
n8  = 11111   (11111 | 01111)
n16 = 11111   (11111 | 01111)
n+1 = 100000  (11111 + 1)
tableSizeFor:32
複製代碼

HashMap(Map<? extends K, ? extends V> m) 構造函數

咱們來看下最後一個構造函數,參數是一個Map ,loadFactor 設置爲默認的 0.75,而後putMapEntries(m, false) 函數,把參數Map 的值拷貝到構造的HashMap 中去。
接着咱們看下putMapEntries(m, false) 這個函數的具體實現.net

putMapEntries(m, false) 函數
/** * 在 putAll 和 構造函數 中被調用 * * @param m the map * @param 構造函數調用是傳 false,不然傳true */
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            if (table == null) { // 構造函數調用是 table = null
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            else if (s > threshold)
                resize();
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }
複製代碼

能夠看到,當構造函數調用putMapEntries(Map<? extends K, ? extends V> m, boolean evict) 函數時,經過參數map 的size 設置threshold ,而後調用putVal() 函數設置內容。putVal() 咱們在後面講 put 的時候再具體分析。日誌

小結

咱們看完HashMap 的構造函數,能夠知道他們的主要做用就是 設置loadFactorthreshold

  • loadFactor 負載因子, 通常都不會設置,默認爲0.75
  • threshold 擴容伐值,使用 initialCapacity或者 map.size 經過tableSizeFor(int cap) 函數獲得。爲大於等於且最接近的2的冪次方的值
相關文章
相關標籤/搜索