JDK1.8 HashMap中put源碼分析

1、存儲結構

     在JDK1.8以前,HashMap採用桶+鏈表實現,本質就是採用數組+單向鏈表組合型的數據結構。它之因此有至關快的查詢速度主要是由於它是經過計算散列碼來決定存儲的位置。HashMap經過key的hashCode來計算hash值,不一樣的hash值就存在數組中不一樣的位置,當多個元素的hash值相同時(所謂hash衝突),就採用鏈表將它們串聯起來(鏈表解決衝突),放置在該hash值所對應的數組位置上。結構圖以下:html

image

    圖中,紫色部分表明哈希表,也稱爲哈希數組,數組中每一個元素都是一個單鏈表的頭結點,鏈表是用來解決衝突的,若是不一樣的key映射獲得了數組的同一位置處,就將其放入單鏈表。java

 

    在JDK1.8中,HashMap的存儲結構已經發生變化,它採用數組+鏈表+紅黑樹這種組合型數據結構。當hash值發生衝突時,會採用鏈表或者紅黑樹解決衝突。當同一hash值的結點數小於8時,則採用鏈表,不然,採用紅黑樹。這個重大改變,主要是提升查詢速度。它的結構圖以下:面試

 

2、put方法

之因此先介紹存儲結構,是爲了更好的理解put方法。數組

public put(K key, V value) 
{
    return putVal(hash(key), key, value, false, true);
}
put方法調用了putVal方法,那咱們再來看看它。
/*
Parameters:
    hash hash for key
    key the key
    value the value to put
    onlyIfAbsent if true, don't change existing value
    evict if false, the table is in creation mode.
Returns:
    previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 若是table爲空,或者尚未元素時,則擴容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 若是首結點值爲空,則建立一個新的首結點。
    // 注意:(n - 1) & hash纔是真正的hash值,也就是存儲在table位置的index。在1.6中是封裝成indexFor函數。
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {    // 到這兒了,就說明碰撞了,那麼就要開始處理碰撞。
            Node<K,V> e; K k;
            // 若是在首結點與咱們待插入的元素有相同的hash和key值,則先記錄。
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode) // 若是首結點的類型是紅黑樹類型,則按照紅黑樹方法添加該元素
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else// 到這一步,說明首結點類型爲鏈表類型。
                    for (int binCount = 0; ; ++binCount) {
                        // 若是遍歷到末尾時,先在尾部追加該元素結點。
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            // 當遍歷的結點數目大於8時,則採起樹化結構。
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                                break;
                        }
                        // 若是找到與咱們待插入的元素具備相同的hash和key值的結點,則中止遍歷。此時e已經記錄了該結點
                        if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
            // 代表,記錄到具備相同元素的結點
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);  // 這個是空函數,能夠由用戶根據須要覆蓋
                return oldValue;
            }
        }
    ++modCount;
    // 當結點數+1大於threshold時,則進行擴容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict); // 這個是空函數,能夠由用戶根據須要覆蓋
    return null;
}

 

參考:數據結構

一、JDK1.8HashMap原理和源碼分析(java面試收藏)app

二、Java類集框架之HashMap(JDK1.8)源碼剖析框架

相關文章
相關標籤/搜索