1. 程式人生 > >HashMap原始碼分析(四)put-jdk8-紅黑樹的引入

HashMap原始碼分析(四)put-jdk8-紅黑樹的引入

HashMap

jdk8以後他的邏輯結構發生了一點變化:


大概就是這個意思:

當某一個點上的元素數量打到一定的閾值的時候,連結串列會變成一顆樹,這樣在極端情況下(所有的元素都在一個點上,整個就以連結串列),一些操作的時間複雜度有O(n)變成了O(logn)。

分析原始碼;

一.還是先看下put方法,證明一下上面的圖基本是對的:

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
 }


final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab;
	Node<K,V> p; 
	int n, i;
	//如果當前map中無資料,執行resize方法。並且返回n
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
	 //如果要插入的鍵值對要存放的這個位置剛好沒有元素,那麼把他封裝成Node物件,放在這個位置上就完事了
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
	//否則的話,說明這上面有元素
        else {
            Node<K,V> e; K k;
	    //如果這個元素的key與要插入的一樣,那麼就替換一下,也完事。
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
	    //1.如果當前節點是TreeNode型別的資料,執行putTreeVal方法
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
		//還是遍歷這條鏈子上的資料,跟jdk6沒什麼區別
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
			//2.完成了操作後多做了一件事情,判斷,並且可能執行treeifyBin方法
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    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) //true || --
                    e.value = value;
		   //3.
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
	//判斷閾值,決定是否擴容
        if (++size > threshold)
            resize();
	    //4.
        afterNodeInsertion(evict);
        return null;
    }



    // Callbacks to allow LinkedHashMap post-actions
    void afterNodeAccess(Node<K,V> p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node<K,V> p) { }

3.4點:

這2個方法是空的,註釋上寫著這是為LinkedHashMap(HashMap的子類)留的回撥函式。方便LinkedHashMao的可開發,所以不管他。

2.if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);

這個方法就是判斷一下當前走到的這個地方(連結串列位置),長度是否達到閾值,需要把連結串列換成樹的形式

也就是執行treeifyBIn方法,當然這裡要是換成樹的形式的話,裡面的元素肯定也要換成TreeNode型別的

1.if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

所以說這裡就很好理解了,看下你是不是樹結構的,如果是數結構就以樹的形式去處理,而不是以之前的單鏈表的形式,傻傻的去一個個遍歷

可以看到。put方法與jdk6相比,多出了有關treeNode的處理,同時也少了key為null的判斷,記得1.6中的key==null是首先判斷並且給他找位置的。

這個多虧了hash方法,他的傳入引數從原先的int值的hashCode變成了物件,支援null的傳入,所以null傳進去得到0的hashCode也能當做普通值一般化處理了

  static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
還有之前的indefFor()方法消失 了,直接用(tab.length-1)&hash,所以看到這個,代表的就是陣列的下角標。

二.TreeNode

簡單的說,不管方法,裡面就包含4個屬性。

TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;

三.treeifyBin

這個方法就是將容器中的node變成treeNode。

方法的引數是Node[] ,int hash,指定需要tree化的是哪個陣列,對應的哪個下角標所連出來的連結串列。

final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; 
	Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
	    //Node e=tab[該hash對應的角標],e就是這個角標下的第一個元素。
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
		//replacementTreeNode == new TreeNode(),就是包裝了一個TreeNode物件
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
			//遍歷連結串列上的第一個元素的時候,t1==null,將p賦值給hd
			//也就是先記錄一下,方便後面的元素記錄pre,next
                    hd = p;
                else {
		//現在p是個tree了,pre記錄上一個元素
                    p.prev = tl;
		    //順便把自己的引用在上一個元素上做記錄
                    tl.next = p;
                }
		//將當前操作的元素的引用傳遞給t1
                tl = p;
		//遍歷整個連結串列,直到沒有元素。
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
	    //遍歷完了,再執行hd.treeify方法
	    //hd=p是在t1==null時執行,也就是隻有在第一個元素的時候執行了一次
		//所以hd代表的是這個樹的根。
                hd.treeify(tab);
        }
    }