通過源碼分析HashMap的實現原理

1. HashMap由數組+鏈表組成的,數組是HashMap的主體,鏈表則是主要爲了解決哈希衝突而存在的,如果定位到的數組位置不含鏈表(當前entry的next指向null),
那麼對於查找,添加等操作很快,僅需一次尋址即可;如果定位到的數組包含鏈表,對於添加操作,其時間複雜度爲O(n),首先遍歷鏈表,存在即覆蓋,否則新增;
對於查找操作來講,仍需遍歷鏈表,然後通過key對象的equals方法逐一比對查找。所以,性能考慮,HashMap中的鏈表出現越少,性能纔會越好

2. HashMap的主幹是一個Entry數組。Entry是HashMap的基本組成單元,每一個Entry包含一個key-value鍵值對
    //HashMap的主幹數組,可以看到就是一個Entry數組,初始值爲空數組{},主幹數組的長度一定是2的次冪
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
    
    Entry是HashMap中的一個靜態內部類
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;//存儲指向下一個Entry的引用,單鏈表結構
        int hash;//對key的hashcode值進行hash運算後得到的值,存儲在Entry,避免重複計算

        /**
        * Creates new entry.
        */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
        
        
3. 其他幾個重要字段
    //實際存儲的key-value鍵值對的個數
    transient int size;
    //閾值,當table == {}時,該值爲初始容量(初始容量默認爲16);當table被填充了,也就是爲table分配內存空間後,threshold一般爲 capacity*loadFactory。HashMap在進行擴容時需要參考threshold
    int threshold;
    //負載因子,代表了table的填充度有多少,默認是0.75
    final float loadFactor;
    //用於快速失敗,由於HashMap非線程安全,在對HashMap進行迭代時,如果期間其他線程的參與導致HashMap的結構發生變化了(比如put,remove等操作),需要拋出異常ConcurrentModificationException
    transient int modCount;
    
4. HashMap中比較重要的put(K key, V value)方法源碼解析
    public V put(K key, V value) {
        //如果table數組爲空數組{},進行數組填充(爲table分配實際內存空間),入參爲threshold,此時threshold爲initialCapacity 默認是1<<4(24=16)
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
       //如果key爲null,存儲位置爲table[0]或table[0]的衝突鏈上
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);//對key的hashcode進一步計算,確保散列均勻
        int i = indexFor(hash, table.length);//獲取在table中的實際位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            //如果該對應數據已存在,執行覆蓋操作。用新value替換舊value,並返回舊value
            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++;//保證併發訪問時,若HashMap內部結構發生變化,快速響應失敗
        addEntry(hash, key, value, i);//新增一個entry
        return null;
    }
    
5. HashMap中比較重要的get(Object key)方法
     public V get(Object key) {
     //如果key爲null,則直接去table[0]處去檢索即可。
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
        return null == entry ? null : entry.getValue();
    }
    
        /**
        * get方法的實現相對簡單,key(hashcode)-->hash-->indexFor-->最終索引位置
        * 找到對應位置table[i],再查看是否有鏈表,遍歷鏈表,通過key的equals方法比對查找對應的記錄
        */
    final Entry<K,V> getEntry(Object key) {
            
        if (size == 0) {
            return null;
        }
        //通過key的hashcode值計算hash值
        int hash = (key == null) ? 0 : hash(key);
        //indexFor (hash&length-1) 獲取最終數組索引,然後遍歷鏈表,通過equals方法比對找出對應記錄
        for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) {
            Object k;
            if (e.hash == hash && 
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    } 

6. HashMap中重要的一點:HashMap存儲自定義類型鍵值的時候,如果自定義對象作爲key存在,這時要保證對象唯一,必須要同時重寫對象中的equals方法和hashcode方法

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