hashmap之put方法詳解

hashmap之put方法詳解

存入鍵值對put:

參數:鍵key、值value

返回值value:

  1. 如果put時map裏已經存在同一個key,返回put之前的key所對應的value;
  2. 如果put時map裏不存在同一個key,返回一個null。

作用:存入鍵值對

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
調用的是putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict)

hash

參數:無

返回值:一個int類型的整數,稱爲hash值。注意不是hash碼,hashCode()計算的纔是hash碼

作用:計算對象的hash值

static final int hash(Object key) {
        int h;
        //key是空就返回0,不空就返回hashcode異或hashcode右移16位的結果。
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

putVal

參數:key的hash值、key、value、onlyIfAbsent:true表示不能修改任意結點、evict:false表示存放結點的數組table在創建模式。

返回值:鍵值對插入的那個位置之前的位置結點的value

作用:插入鍵值對。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; //用於指向table
        Node<K,V> p; //用於指向數組table的索引處的結點
        int n, i;//n表示table的長度
        如果table是未初始化或者table的長度是0,就重新初始化table,長度是16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        如果索引處的結點是空,就直接在此處新創建一個結點存入相關數據。
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        否則
        else {
            Node<K,V> e; //用來表示鍵值對應該存放的位置的結點
            K k;//用來表示鍵值對應該存放的位置的結點的key
            如果p是鍵值對應該存放的位置的結點,把p賦值給e
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            否則判斷p是不是在紅黑樹裏,如果在就去樹裏找鍵值對應該存放的位置的結點並賦值給e
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            					如果不在,就遍歷鏈表
                for (int binCount = 0; ; ++binCount) {
                						如果p的下一個結點是null,則p的下一個結點就是e,在此處新建結點,並且判斷新建結點後鏈表的長度是不是大於8,是就把這個鏈表轉成紅黑樹,結束
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    					如果e是鍵值對應該存放的位置的結點,就找到了,終止循環
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            e是鍵值對應該存放的位置的結點,所以只替換e的value就達到目的了,返回替換前e的value,結束函數。
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        //新建了結點,把map結構改變計數器+1
        ++modCount;
        //++size是新的size,新的size要是比計算的閾值大就擴容數組,表明快滿了
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

resize

參數:無

返回值:數組

作用:擴充數組。

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;原來的數組
        int oldCap = (oldTab == null) ? 0 : oldTab.length;原來的數組長度
        int oldThr = threshold;原來的閾值
        int newCap, newThr = 0;新的數組長度、新的數組閾值
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;如果原來的數組長度已經超過了數組規定 的最大長度,返回原來的數組,結束擴容,因爲不能擴容了。
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
				如果原來的數組長度已經超過了默認數組長度16,但是新數組長度小於最大長度,把
				閾值擴大2倍。
        }
		
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;如果舊容量是0,舊閾值>0,那嘛新容量設置成舊閾值。
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;如果舊容量是0,舊閾值也是0,那麼把新容量設置成默認的容量,把新閾值設置成默認加載因子
			*默認容量。
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
		
		設置新閾值
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
		hashmap的閾值等於新閾值
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
		根據新容量創建新數組
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
		放入新的數組時會分爲樹中和鏈表,如果原先的就是鏈表,則會對原鏈表的數據進行重新排序。如果原先是紅黑樹,則會將舊樹中的node分離。
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)如果舊數組的結點是單獨的結點,直接計算索引,放入新數組
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)否則、如果舊數組的結點在紅黑樹裏,就調整紅黑樹結構
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order否則,如果舊數組的結點在鏈表裏
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

總結

hashmap的put函數過程:1.調用hash;2.調用putVal.

首先根據hash()函數計算出key的hash值,【注意不是hash碼,hashCode()計算的纔是hash碼】,然後把這個hash值和key、value、onlyIfAbsent、evict一共五個參數傳遞到putVal函數。

onlyIfAbsent:是true時表示不能修改hashmap裏的任意結點,顯然這裏應該傳入false,因爲往hashmap裏面put存鍵值對是要修改結點的【兩種情況:1.覆蓋原來的結點 2.新增一個結點】
evict:是false代表table正在構建,也是不能操作table,因爲要操作table,這裏應該傳入true。

然後,在putVal裏面:

  1. 判斷數組table是不是沒有初始化或數組的長度是0,滿足任意一個條件,就初始化數組。
初始化時調用resize()方法,數組長度設置爲16
  1. 根據hash值計算索引,索引處的結點記爲p,

    如果p是null,那就沒有hash衝突,直接在此處調用newNode方法創建一個新結點。否則,

    1. 判斷p是不是key應該存入的位置的結點,如果是,暫時把e=p,e表示key應該存入的位置的結點,如果不是進行下一步
    2. 判斷p是不是在紅黑樹裏,如果是調用putTreeVal把key與value存入樹,並返回key應該存入的位置的結點給e,否則進行下一步
    3. 此時說明p在鏈表裏,那麼遍歷鏈表,e=p.next,如果e是空,直接新建一個結點存入數據,之後再判斷此時的鏈表長度是不是大於8,是就轉換成紅黑樹,結束遍歷。
    4. 如果此時的e真的是key應該存入的位置的結點,那就不用往下找了,直接結束遍歷;

    如果e不是null,把e的value替換成要存入的value即可。然後結束putVal函數。

修改hashmap的結構改變次數計數器。

++size是新的size,新的size要是比計算的閾值大就擴容數組,表明快滿了,就對數組擴容。

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