1. 程式人生 > >Jdk1.7 HashMap原始碼分析與思考

Jdk1.7 HashMap原始碼分析與思考

HashMap類圖

HashMap類圖 根據類圖可知,HashMap實現了三個介面繼承了一個抽象類。

實現的介面概覽

  • 介面如下:
  1. java.util.Map

    其中Map介面中定義了Map基本操作方法,詳細介面描述請參考java.util.Map介面描述. Map介面中採用一個內部介面java.util.Map.Entry來封裝每一個鍵值對,這樣Map中的元素就變成了Map.Entry<K,V>的對映項。

  2. java.lang.Cloneable

    實現此介面可以對HashMap進行克隆, HashMap預設為淺克隆. 更多瞭解請點選: HashMap的clone方法(淺克隆) HashMap物件的深層克隆

繼承的抽象類概覽

  • 抽象類如下
  1. java.util.AbstractMap

    AbstractMap 是 Map 介面的的實現類之一,也是 HashMap, TreeMap, ConcurrentHashMap 等類的父類。 Java 集合深入理解(15):AbstractMap

HashMap 成員變數

HashMap定義了13個成員變數。 HashMap成員變數

成員變數1:DEFAULT_INITIAL_CAPACITY

    /**
    * The default initial capacity - MUST be a power of two.
    */
   static final
int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

此程式碼段定義HashMap初始容量大小為16,必須為2的冪,原始碼中使用了移位運算子計算出HashMap的初始容。 問題1: 為什麼原始碼中不直接寫成16而要使用移位運算呢?. 答案1: 我的理解是因為作業系統最終會使用二進位制進行計算,這樣寫省略了轉換過程,提高了效率。 問題2: 為什麼初始容量大小是16,而不是5(奇數代表),8(2的3次冪代表),10(偶數代表)?. 答案2: 待補充

成員變:2:MAXIMUM_CAPACITY

/**
    * The maximum capacity, used if a higher value is implicitly specified
    * by either of the constructors with arguments.
    * MUST be a power of two <= 1<<30.
    */
static final int MAXIMUM_CAPACITY = 1 << 30;

定義HashMap最大容量,如果使用者指定大於這個值,HashMap會使用該容量。1<<30 為2 ^ 30=1073741824。 問題1: 那麼為什麼定義是2^30 ?. 答案1: 待補充

成員變:3:DEFAULT_LOAD_FACTOR

/**
  * The load factor used when none specified in constructor.
  */
 static final float DEFAULT_LOAD_FACTOR = 0.75f;

HashMap預設的負載因子,用於擴容。 問題1: 那麼為什麼定義是0.75f ?. 答案1: 待補充

成員變數4:EMPTY_TABLE

/**
  * An empty table instance to share when the table is not inflated.
  */
 static final Entry<?,?>[] EMPTY_TABLE = {};

定義共享的空表例項。 使用static final關鍵字修飾變數,說明此例項本身虛擬機器載入後放入執行時資料區的方法區,全域性共享,例項本身不可變,但是因為是容器型別的例項變數,所以容器中的物件是可以被修改的。

成員變數5:table

/**
  * The table, resized as necessary. Length MUST Always be a power of two.
  */
 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

定義HashMap內部資料結構,型別為Entity,長度由容量指定。

成員變數:6:size

/**
  * The number of key-value mappings contained in this map.
  */
 transient int size;

HashMap容量大小變數 使用transient關鍵字說明此變數不需要序列化。

成員變數7:threshold

/**
  * The next size value at which to resize (capacity * load factor).
  * @serial
  */
 // If table == EMPTY_TABLE then this is the initial capacity at which the
 // table will be created when inflated.
 int threshold;

HashMap的極限容量,擴容臨界點(容量和載入因子的乘積)

成員變數8:loadFactor

/**
  * The load factor for the hash table.
  *
  * @serial
  */
 final float loadFactor;

hash table負載因子變數定義 使用final關鍵字修飾後,賦值一次,賦值後不能再被改變

成員變數9:modCount

/**
  * The number of times this HashMap has been structurally modified
  * Structural modifications are those that change the number of mappings in
  * the HashMap or otherwise modify its internal structure (e.g.,
  * rehash).  This field is used to make iterators on Collection-views of
  * the HashMap fail-fast.  (See ConcurrentModificationException).
  */
 transient int modCount;

HashMap修改的次數變數定義。

成員變數10:ALTERNATIVE_HASHING_THRESHOLD_DEFAULT

/**
  * The default threshold of map capacity above which alternative hashing is
  * used for String keys. Alternative hashing reduces the incidence of
  * collisions due to weak hash code calculation for String keys.
  * <p/>
  * This value may be overridden by defining the system property
  * {@code jdk.map.althashing.threshold}. A property value of {@code 1}
  * forces alternative hashing to be used at all times whereas
  * {@code -1} value ensures that alternative hashing is never used.
  */
 static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

預設的容器閥值大小。

成員變數11:hashSeed

 /**
  * A randomizing value associated with this instance that is applied to
  * hash code of keys to make hash collisions harder to find. If 0 then
  * alternative hashing is disabled.
  */
 transient int hashSeed = 0;

雜湊種子(hashSeed),這個hashSeed用於優化雜湊函式,預設為0是不使用替代雜湊演算法,但是也可以自己去設定hashSeed的值,以達到優化效果。

成員變數12:entrySet

// Views

 private transient Set<Map.Entry<K,V>> entrySet = null;

定義Map.Entry<K,V>集合。

成員變數13:serialVersionUID

private static final long serialVersionUID = 362498820763181265L;

隨機生成的序列化ID

HashMap四個建構函式

建構函式1: HashMap()

/**
  * Constructs an empty <tt>HashMap</tt> with the default initial capacity
  * (16) and the default load factor (0.75).
  */
 public HashMap() {
     this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
 }

HashMap預設的建構函式,預設容量大小為16,負載因子為0.75,此兩個成員變數在上文中有詳細描述。

建構函式2:HashMap(int initialCapacity)

/**
  * Constructs an empty <tt>HashMap</tt> with the specified initial
  * capacity and the default load factor (0.75).
  *
  * @param  initialCapacity the initial capacity.
  * @throws IllegalArgumentException if the initial capacity is negative.
  */
 public HashMap(int initialCapacity) {
     this(initialCapacity, DEFAULT_LOAD_FACTOR);
 }

可以指定容量大小的HashMap構造,負載因子使用預設的0.75.

建構函式3: HashMap(Map<? extends K, ? extends V> m)

 /**
  * Constructs a new <tt>HashMap</tt> with the same mappings as the
  * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
  * default load factor (0.75) and an initial capacity sufficient to
  * hold the mappings in the specified <tt>Map</tt>.
  *
  * @param   m the map whose mappings are to be placed in this map
  * @throws  NullPointerException if the specified map is null
  */
 public HashMap(Map<? extends K, ? extends V> m) {
     this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                   DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
      //根據計算出來的擴容閥值初始化table,hashSeed, threshold            
     inflateTable(threshold);
 	 //將傳入的Map資料迴圈放入此HashMap中
     putAllForCreate(m);
 }

建構函式4:HashMap(int initialCapacity, float loadFactor)

   /**
  * Constructs an empty <tt>HashMap</tt> with the specified initial
  * capacity and load factor.
  *
  * @param  initialCapacity the initial capacity
  * @param  loadFactor      the load factor
  * @throws IllegalArgumentException if the initial capacity is negative
  *         or the load factor is nonpositive
  */
 public HashMap(int initialCapacity, float loadFactor) {
   //如果容量小於0,則拋異常
     if (initialCapacity < 0)
         throw new IllegalArgumentException("Illegal initial capacity: " +
                                            initialCapacity);
        //如果設定的容量大於定義的最大容量,則取最大容量值                                    
     if (initialCapacity > MAXIMUM_CAPACITY)
         initialCapacity = MAXIMUM_CAPACITY;
         //如果負載因子小於0或者是非法值則拋異常
     if (loadFactor <= 0 || Float.isNaN(loadFactor))
         throw new IllegalArgumentException("Illegal load factor: " +
                                            loadFactor);
 	//初始化負載因子和擴容閥值
     this.loadFactor = loadFactor;
     threshold = initialCapacity;
     //空實現,不需關注
     init();
 }

其他三個構造器都會呼叫此構造器。

主要方法詳解

public V put(K key, V value)方法

將指定的值與此對映中的指定鍵相關聯。 如果對映先前包含鍵的對映,則替換舊值。原始碼如下:

 /**
  * Associates the specified value with the specified key in this map.
  * If the map previously contained a mapping for the key, the old
  * value is replaced.
  *
  * @param key key with which the specified value is to be associated
  * @param value value to be associated with the specified key
  * @return the previous value associated with <tt>key</tt>, or
  *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
  *         (A <tt>null</tt> return can also indicate that the map
  *         previously associated <tt>null</tt> with <tt>key</tt>.)
  */
 public V put(K key, V value) {
    //如果為陣列為空,則進行初始化操作。
     if (table == EMPTY_TABLE) {
         inflateTable(threshold);
     }
     if (key == null)
     //如果key為空,則將值value方到陣列0位置上,如果0位置有值則替換
         return putForNullKey(value);
      //計算key的hash值
     int hash = hash(key);
     //根據hash值和儲存資料的陣列長度計算位置索引
     int i = indexFor(hash, table.length);
     //查詢對應位置是否有值,如果有值則使用新值替換
     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;
             //HashMap 空實現,可忽略
             e.recordAccess(this);
             return oldValue;
         }
     }
 	//記錄修改次數加1
     modCount++;
     //增加key-value對映到陣列中
     addEntry(hash, key, value, i);
     return null;
 }

HashMap put方法流程圖

Created with Raphaël 2.2.0開始儲存單元為Entry型別的陣列table非空輸入的key是否為空Entry陣列0位子資料是否為空將值value儲存到陣列的0位置計算key的hash值根據hash值和table陣列長度計算位置索引index索引位置有Entry資料hash值和key都相等新值覆蓋舊值返回舊值增加新的Entry到索引位置 陣列大小大約等於擴容閥值並且索引位置非空擴容到原容量的2倍且小於最大容量構建一個新的Entry物件放到索引為bucketIndex的位置,並且將該位置原先的物件設定為新物件的next構成連結串列結束將值value更新到陣列的0位置HashMap容量小於1 << 30容量大於1取接近定義容量的最小2冪數賦值容量值capacity根據負載因子和容量capacity計算擴容閥值threshold初始化儲存key-value陣列table容量初始化HashSeed如果容量非法則取1返回定義的最大容量1 << 30yesnoyesnoyesnoyesnoyesnoyesnoyesnoyesno

初始化陣列方法private void inflateTable(int toSize)

/**
  * Inflates the table.
  */
 private void inflateTable(int toSize) {
     // Find a power of 2 >= toSize
     //查詢大於toSize的最小2的冪數,例如傳入的toSize=23,那麼capacity為2^6=32
     int capacity = roundUpToPowerOf2(toSize);
     //根據容量和負載因子計算擴充套件閾值,當容量達到此閥值時,HashMap進行擴容。
     threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
     //初始化EntiEntry<K,V>[] 陣列儲存大小
     table = new Entry[capacity];
     //初始化HashSeed
     initHashSeedAsNeeded(capacity);
 }

克隆和Map引數構造器中使用的關鍵方法private void putAllForCreate(Map<? extends K, ? extends V> m)方法

//迴圈遍歷傳入的Map集合,複製到新的Map中
 private void putAllForCreate(Map<? extends K, ? extends V> m) {
     for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
         putForCreate(e.getKey(), e.getValue());
 }
 /**
  * This method is used instead of put by constructors and
  * pseudoconstructors (clone, readObject).  It does not resize the table,
  * check for comodification, etc.  It calls createEntry rather than
  * addEntry.
  */
 private void putForCreate(K key, V value) {
    //根據鍵計算hash值,如果為空則hash值為0
     int hash = null == key ? 0 : hash(key);
     //根據hash值和表長度計算在陣列中的位置。
     int i = indexFor(hash, table.length);

     /**
      * Look for preexisting entry for key.  This will never happen for
      * clone or deserialize.  It will only happen for construction if the
      * input Map is a sorted map whose ordering is inconsistent w/ equals.
      */
      //1.如果當前儲存的陣列的位置存在資料,則替換新值
     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
         Object k;
         if (e.hash == hash &&
             ((k = e.key) == key || (key != null && key.equals(k)))) {
             e.value = value;
             return;
         }
     }
 	//2.否則插入資料
     createEntry(hash, key, value, i);
 }

HashMap擴容方法void addEntry(int hash, K key, V value, int bucketIndex)

/**
  * Adds a new entry with the specified key, value and hash code to
  * the specified bucket.  It is the responsibility of this
  * method to resize the table if appropriate.
  *
  * Subclass overrides this to alter the behavior of put method.
  */
 void addEntry(int hash, K key, V value, int bucketIndex) {
     if ((size >= threshold) && (null != table[bucketIndex])) {
         resize(2 * table.length);
         hash = (null != key) ? hash(key) : 0;
         bucketIndex = indexFor(hash, table.length);
     }

     createEntry(hash, key, value, bucketIndex);
 }

HashMap解決hash衝突方法void createEntry(int hash, K key, V value, int bucketIndex)

/**
  * Like addEntry except that this version is used when creating entries
  * as part of Map construction or "pseudo-construction" (cloning,
  * deserialization).  This version needn't worry about resizing the table.
  *
  * Subclass overrides this to alter the behavior of HashMap(Map),
  * clone, and readObject.
  */
 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++;
 }

總結