hashMap底層put和get方法邏輯


先看轉載的一邊文章:https://mp.weixin.qq.com/s/fZRPogkkUfBnhbZQB5r-uw

1.hashmap put方法的實現:

 public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        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;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

首先對key做null檢查。如果key是null,會被存儲到table[0],因為null的hash值總是0。

key的hashcode()方法會被調用,然后計算hash值。hash值用來找到存儲Entry對象的數組的索引。有時候hash函數可能寫的很不好,所以JDK的設計者添加了另一個叫做hash()的方法,它接收剛才計算的hash值作為參數。如果你想了解更多關於hash()函數的東西,可以參考:hashmap中的hash和indexFor方法

indexFor(hash,table.length)用來計算在table數組中存儲Entry對象的精確的索引。

在我們的例子中已經看到,如果兩個key有相同的hash值(也叫沖突),他們會以鏈表的形式來存儲。所以,這里我們就迭代鏈表。

· 如果在剛才計算出來的索引位置沒有元素,直接把Entry對象放在那個索引上。

· 如果索引上有元素,然后會進行迭代,一直到Entry->next是null。當前的Entry對象變成鏈表的下一個節點。

· 如果我們再次放入同樣的key會怎樣呢?邏輯上,它應該替換老的value。事實上,它確實是這么做的。在迭代的過程中,會調用equals()方法來檢查key的相等性(key.equals(k)),如果這個方法返回true,它就會用當前Entry的value來替換之前的value。

  2.hashMap get方法的解析:

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }

當你傳遞一個key從hashmap總獲取value的時候:

對key進行null檢查。如果key是null,table[0]這個位置的元素將被返回。

key的hashcode()方法被調用,然后計算hash值。

indexFor(hash,table.length)用來計算要獲取的Entry對象在table數組中的精確的位置,使用剛才計算的hash值。

在獲取了table數組的索引之后,會迭代鏈表,調用equals()方法檢查key的相等性,如果equals()方法返回true,get方法返回Entry對象的value,否則,返回null。

 

3.要牢記以下關鍵點:

  1. · HashMap有一個叫做Entry的內部類,它用來存儲key-value對。
  2. · 上面的Entry對象是存儲在一個叫做table的Entry數組中。
  3. · table的索引在邏輯上叫做“桶”(bucket),它存儲了鏈表的第一個元素。
  4. · key的hashcode()方法用來找到Entry對象所在的桶。
  5. · 如果兩個key有相同的hash值,他們會被放在table數組的同一個桶里面。
  6. · key的equals()方法用來確保key的唯一性。
  7. · value對象的equals()和hashcode()方法根本一點用也沒有。

簡單地說,HashMap 在底層將 key-value 當成一個整體進行處理,這個整體就是一個 Entry 對象。HashMap 底層采用一個 Entry[] 數組來保存所有的 key-value 對,當需要存儲一個 Entry 對象時,會根據hash算法來決定其在數組中的存儲位置,在根據equals方法決定其在該數組位置上的鏈表中的存儲位置;當需要取出一個Entry時, 也會根據hash算法找到其在數組中的存儲位置,再根據equals方法從該位置上的鏈表中取出該Entry。

HashMap的resize(rehash)

 

當hashmap中的元素越來越多的時候,碰撞的幾率也就越來越高(因為數組的長度是固定的),所以為了提高查詢的效率,就要對hashmap的數組進行擴容,數組擴容這個操作也會出現在ArrayList中,所以這是一個通用的操作,很多人對它的性能表示過懷疑,不過想想我們的“均攤”原理,就釋然了,而在hashmap數組擴容之后,最消耗性能的點就出現了:原數組中的數據必須重新計算其在新數組中的位置,並放進去,這就是resize。 

         那么hashmap什么時候進行擴容呢?當hashmap中的元素個數超過數組大小*loadFactor時,就會進行數組擴容,loadFactor的默認值為0.75,也就是說,默認情況下,數組大小為16,那么當hashmap中元素個數超過16*0.75=12的時候,就把數組的大小擴展為2*16=32,即擴大一倍,然后重新計算每個元素在數組中的位置,而這是一個非常消耗性能的操作,所以如果我們已經預知hashmap中元素的個數,那么預設元素的個數能夠有效的提高hashmap的性能。比如說,我們有1000個元素new HashMap(1000), 但是理論上來講new HashMap(1024)更合適,不過上面annegu已經說過,即使是1000,hashmap也自動會將其設置為1024。 但是new HashMap(1024)還不是更合適的,因為0.75*1000 < 1000, 也就是說為了讓0.75 * size > 1000, 我們必須這樣new HashMap(2048)才最合適,既考慮了&的問題,也避免了resize的問題。 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM