Java HashMap 實現源代碼分析

最近在用HashMap的時候,碰到一些問題,有疑惑,遂查看源代碼分析了一下。

我們知道,HashMap<K, V>是以鍵值對來讀寫數據的,那它底層用於存放Value的什麼?又如何判斷兩個Key是否相等呢?

首先說說哈希表的一些相關概念,可能有些名詞會有不同的叫法。

  • 容量:散列表中散列數組大小
  • 散列運算:key->散列值(散列數組下標)的算法, h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);
  • 散列桶:散列值相同的元素的“線性集合”
  • 加載因子(loadFactor):散列數組的加載率,一般小於75%比較理想(即元素數量/散列數組大小)。
    • threshold = (int)(newCapacity * loadFactor);//閾值=新的容量*加載因子,當新增元素時達到了閾值就要再次擴容
  • 散列查找:根據Key計算散列值,根據散列值(下標)找到在散列桶,在散列桶中順序比較Key,如果相同(Key的hascode相同、且equals()爲true)就返回Value;散列表中Value可以重複,只要其對應的Key不一樣即可;如果Key相同,新添加的元素會覆蓋原來的Value。
再來看看HashMap類中的幾個主要成員變量:
  • Entry[] table;//Entry<K, V>型的數組,用於存放Value,每個Entry元素裏有Key, Value, hash與next(指向下一個Entry)
  • int size;//所有Value的數量,Map的實際容量
  • int threshold;//閥值,超過之後要對table擴容
  • final float loadFactor;//加載因子
table實際上是一個Entry<K, V>型的數組,它就是用於存放鍵值對的,而不同的Key可能會算出相同的hash code,因此,table的某一個下標可能會有多個Entry, 而Entry裏有一個Entry<K, V> next的屬性,用於指向下一個Entry——實際上就是一個鏈表,這樣,key相同的Value也能放到table的同一個置,通過next來引用下一個Entry.
Entry[] table
Entry Entry Entry Entry Entry
   
e.next
   
e.next
   
 null
同一個散列桶中有多個Entry時

Entry:HashMap中的內部類
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;

     /*Entry 這裏是一個鏈表,也就是散列桶中的線性集合:
     *  當hash code相同時,位於table的同一個下標中,能過next連成一個線性集合。
     */
        Entry<K,V> next;
        final int hash;
      /**
         * Creates new entry.
         */

        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
}

HashMap的put方法(增加元素):
public V put(K key, V value) {
        if (key == null)
//可以以null爲key
            return putForNullKey(value);
        
int hash = hash(key.hashCode());//算出此key的hash code,用位移的算法
        
int i = indexFor(hash, table.length);//用hashcode&table長度,即將hashcode的高位清零,保留低位(與table長度一樣)的位,這樣得到的下標,一定在table的長度範圍之內。
        
          //找到table下標爲i的位置,如果此位置已經有元素即兩個元素的Key的hashcode相同,則說明此散列桶有多個元素
          //從第一個Entry開始,循環e.next遍歷此鏈表並判斷:
          for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
               //如果兩個Entry的hashcoe相同,且equals,那麼則認爲是同一個Key
            
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                    //則把這個新元素替換原來的元素
                e.value = value;
                e.recordAccess(
this);
                    //並返回原來的元素
                
return oldValue;
            }
        }
          //如果table下標爲i的位置爲null,則說明此散列桶爲空,直接將新元素添加到此即可
        modCount++;
        addEntry(hash, key, value, i);*注
        
return null;
}

*注:
 void addEntry(int hash, K key, V value, int bucketIndex) {
     Entry<K,V> e = table[bucketIndex];//e爲null
     table[bucketIndex] = new Entry<K,V>(hash, key, value, e);//新建Entry,next爲null
     if (size++ >= threshold)//如果實際大小超過閥值,則擴容
         resize(2 * table.length);
 }

這是小弟在CSDN發的第一篇博文,如有技術上的錯誤,請大蝦們不吝賜教!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章