淺談HashMap的默認值、屬性和構造器

定義

HashMap基於哈希表的Map實現,以key-value的形式存在,並容許使用 null 值和 null 鍵。繼承AbstractMap,當中實現了Map的重要方法。類定義以下:
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
複製代碼

HashMap綜合了ArrayList和LinkList的優勢
java

  1. 使用數組存儲數據,索引實現O(1) 時間複雜度,全部查找速度比較快。同ArrayList;
  2. 當發生碰撞時,採用鏈表存儲數據,解決了因hash衝突;同LinkList;
  3. 當鏈表過長時,致使鏈表節點的查找性能降低。所以當鏈表長度超過 8 時, 將鏈表轉變成紅黑樹,以將O(n) 時間複雜度提高至O(log n)。

默認值

/**
     * 默認初始容量16,必須是2的冪
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

    /**
     * 最大容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默認負載因子。當鍵值對的數量大於 CAPACITY * 0.75 時,就會觸發擴容
     * 如無參的HashMap構造器,初始化容量爲16,當鍵值對的數量大於 16 * 0.75 = 12時,就會觸發擴容
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 計數閾值。鏈表的元素大於8的時候,鏈表將轉化爲樹
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 計數閾值。resize操做時,紅黑樹的節點數量小於6時使用鏈表來代替樹
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 在轉變成樹以前,還會有一次判斷,只有鍵值對數量大於 64 纔會發生轉換。
     * 這是爲了不在哈希表創建初期,多個鍵值對剛好被放入了同一個鏈表中而致使沒必要要的轉化
     */
    static final int MIN_TREEIFY_CAPACITY = 64;
複製代碼

屬性

/**
     * 存儲數據的哈希表,默認容量16。長度老是2的冪
     */
    transient Node<K,V>[] table;

    /**
     * Entry集合,主要用於迭代功能
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * 實際存在的Node數量,不必定等於table的長度(初始化容量),甚至可能大於它。
     */
    transient int size;

    /**
     * HashMap結構被修改的次數,迭代過程當中遵循Fail-Fast機制。
     * 保證多線程同時修改map時,能及時的發現(操做前備份的count和當前modCount不相等)並拋出異常終止操做。
     */
    transient int modCount;

    /**
     * 擴容閾值,超過(capacity * loadFactor)時,自動擴容容量爲原來的二倍。 
     */
    int threshold;

    /**
     * 負載因子,可計算threshold:threshold = capacity * loadFactor
     */
    final float 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(int initialCapacity) {
            this(initialCapacity, DEFAULT_LOAD_FACTOR);
        }

        public HashMap() {
            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
        }

        public HashMap(Map<? extends K, ? extends V> m) {
            this.loadFactor = DEFAULT_LOAD_FACTOR;
            putMapEntries(m, false);
        }
複製代碼

主要談談HashMap(int initialCapacity, float loadFactor);實例化時,內部調用tableSizeFor(initialCapacity)初始化容量;方法以下數組

/**
     * 根據整數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;
    }
複製代碼

對於求解大於等於它,且爲2的整數次冪的最小值,int n = cap - 1;很關鍵。 假設不執行int n = cap - 1;cap = 8。按照java8的設計思想,由於8>=2^3,結果就應該是8,但結果倒是16.bash

8的二進制:1000
n |= n >>> 1;   n = n | n >>> 1; 1000 | 0100 = 1100
n |= n >>> 2;   n = n | n >>> 2; 1100 | 0011 = 1111
n |= n >>> 4;   n = n | n >>> 4; 1111 | 0000 = 1111
...
n |= n >>> 16; n = 1111 = 15(十進制),最後執行n + 1 = 16
複製代碼
相關文章
相關標籤/搜索