HashMap 源碼分析

構造函數

HashMap 提供了三個構造函數和一個拷貝構造函數:

public HashMap(int initialCapacity, float loadFactor);

public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

public HashMap(Map<? extends K, ? extends V> m);

構造函數

/**
 * 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) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY) {
        initialCapacity = MAXIMUM_CAPACITY;
    } else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) {
        initialCapacity = DEFAULT_INITIAL_CAPACITY;
    }

    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    // Android-Note: We always use the default load factor of 0.75f.

    // This might appear wrong but it's just awkward design. We always call
    // inflateTable() when table == EMPTY_TABLE. That method will take "threshold"
    // to mean "capacity" and then replace it with the real threshold (i.e, multiplied with
    // the load factor).
    threshold = initialCapacity;
    init();
}

這個構造方法需要提供兩個參數,initialCapacityloadFactor

  • 第一個參數表示 hashMap 的初始化大小,默認值為 static final int DEFAULT_INITIAL_CAPACITY = 4; ,且 初始化大小的值必須是 2 的整數次冪。
  • 第二個參數表示 todo

在構造方法中,首先對初始化大小 initialCapacity 進行校正,校正范圍是 4(默認初始化大小) ~ 2^30(最大容量)。

HashMapEntry

static class HashMapEntry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    HashMapEntry<K,V> next;
    int hash;
   }

HashMapEntry 中保存著一個 Key 和 Value ,并持有下一個 HashMapEntry ,很明顯這是一個 <K-V> 鍵值對鏈表。

數據結構

put 方法

/**
 * 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)
        return putForNullKey(value);
    int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
    int i = indexFor(hash, table.length);
    for (HashMapEntry<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);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
  • 從上面代碼可以看出,HashMap 的 Key 是可以為 null 的,在 key == null 的情況下,調用 putForNullKey() 方法特殊處理
  • 正常的情況下,現根據 key 的值計算出 hash 值,并計算出該 hash 值在隊列中的 index。在 table 數組中取出 HashMapEntry 的鏈表,并進行遍歷,如果 hash 值相同且 Key 也相同,說明新插入的 Key 在 鏈表中存在,需要覆蓋舊值,并返回舊值。如果沒有找到,說明這個 Key 并不存在,需要調用 addEntry 添加在 HashMapEntry 的鏈表中。
**
 * 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) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

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

從 addEntry 方法的實現可以看出,當此時 hashmap 的 size 大于等于閾值時,需要將 table 的長度擴大一倍,并調用 createEntry() 方法根據新 Key 的 hash 值 存儲在 table 中。

/**
 * 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) {
    HashMapEntry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
    size++;
}

將 table 中 index = bucketIndex 的 HashMapEntry 作為新節點的下一個 Entry ,并將 table[bucketIndex] 指向新節點。

get 方法

get 方法的思想就是利用 key 的 hash 值獲得在 table 中所處的 index ,并對其進行遍歷,比較 Key 將對應的 Value 取出。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容