【java集合框架源碼剖析系列】java源碼剖析之TreeMap

注:博主java集合框架源碼剖析系列的源碼全部基於JDK1.8.0版本。本博客將從源碼角度帶領大家學習關於TreeMap的知識。

一TreeMap的定義:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
可以看到TreeMap是繼承自AbstractMap同時實現了NavigableMap,Cloneable,Serializable三個接口,其中Cloneable,Serializable這兩個接口基本上是java集合框架中所有的集合類都要實現的接口。

二TreeMap類中的一些重要屬性:

<strong> </strong>private final Comparator<? super K> comparator;
 private transient Entry<K,V> root;
 private transient int size = 0;
 private transient int modCount = 0;
第一個屬性是Comparator<? super K> comparator比較器,從這裏就可以知道TreeMap會運用比較器接口來對插入的元素進行排序。而第二個成員屬性爲Entry<K,V>即爲紅黑樹,紅黑樹是一種數據結構,它和AVL樹一樣是一種自平衡二叉查找樹,該數據結構具備非常高的插入,刪除,查找的效率。Entry被定義爲TreeMap的一個內部類,代碼如下:

 static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;

        /**
         * Make a new cell with given key, value, and parent, and with
         * {@code null} child links, and BLACK color.
         */
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        /**
         * Returns the key.
         *
         * @return the key
         */
        public K getKey() {
            return key;
        }

        /**
         * Returns the value associated with the key.
         *
         * @return the value associated with the key
         */
        public V getValue() {
            return value;
        }

        /**
         * Replaces the value currently associated with the key with the given
         * value.
         *
         * @return the value associated with the key before this method was
         *         called
         */
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;

            return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
        }

        public int hashCode() {
            int keyHash = (key==null ? 0 : key.hashCode());
            int valueHash = (value==null ? 0 : value.hashCode());
            return keyHash ^ valueHash;
        }

        public String toString() {
            return key + "=" + value;
        }
    }
可以看到Entry紅黑樹的代碼一點也不復雜,和普通的二叉樹差不多,僅僅多了一個判斷顏色的屬性boolean color,該屬性默認值爲黑色,即BLACK,關於紅黑樹的具體知識,在此不做過多介紹,博主打算在數據結構與算法那塊進行詳細介紹。可以先點此紅黑樹查看百度百科做初步瞭解。


三TreeMap內部的實現原理:我們首先看一下其構造器

 public TreeMap() {// 構造方法一,默認的構造方法,comparator爲空,即採用自然順序維持TreeMap中節點的順序
        comparator = null;
    }

 public TreeMap(Comparator<? super K> comparator) {// 構造方法二,提供指定的比較器
        this.comparator = comparator;
    }

public TreeMap(Map<? extends K, ? extends V> m) {// 構造方法三,採用自然序維持TreeMap中節點的順序,同時將傳入的Map中的內容添加到TreeMap中
        comparator = null;
        putAll(m);
    }
/** 
*構造方法四,接收SortedMap參數,根據SortedMap的比較器維持TreeMap中的節點順序, 同時通過buildFromSorted(int size, Iterator it, java.io.ObjectInputStream str, V defaultVal)方法將SortedMap中的內容添加到TreeMap中
*/
public TreeMap(SortedMap<K, ? extends V> m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

重點關注構造器二和三,即提供指定的比較器和將傳入的Map參數採用自然序維持節點的順序,因爲很多情況下,不同對象的比較大小的方法是不一樣的,所以很多時候我們需要自己指定比較器。另外可以看到在構造器三種調用了putAll方法,我們來看一下其源碼:

public void putAll(Map<? extends K, ? extends V> map) {
        int mapSize = map.size();
        if (size==0 && mapSize!=0 && map instanceof SortedMap) {
            Comparator<?> c = ((SortedMap<?,?>)map).comparator();
            if (c == comparator || (c != null && c.equals(comparator))) {
                ++modCount;
                try {
                    buildFromSorted(mapSize, map.entrySet().iterator(),
                                    null, null);
                } catch (java.io.IOException cannotHappen) {
                } catch (ClassNotFoundException cannotHappen) {
                }
                return;
            }
        }
        super.putAll(map);
    }

 public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }
我們可以看到在putAll方法中調用了buildFromSorted(int size, Iterator<?> it, java.io.ObjectInputStream str,V defaultVal),該方法的作用即是在線性時間內對數據進行排序(Linear time tree building algorithm from sorted data),看到這裏我們就明白TreeMap排序的原理了,即當使用一個Map集合作爲參數構造一個TreeMap的時候,TreeMap會將Map中的元素先排序,然後排序後的元素put到TreeMap中。其中在TreeMap的putAll方法的最後會調用其父類AbstractMap的putAll方法,在其父類的putAll方法中才會調用put方法。

四TreeMap中的重要函數:

1put方法

public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {//如果比較器 cpr 不爲 null,即表明採用自定義的排序
            do {// do while作用是在以root爲根節點的紅黑樹中根據傳入的key值尋找待插入的位置
                parent = t;
                cmp = cpr.compare(key, t.key);//將待插入節點的值與當前節點比較
                if (cmp < 0)//如果待插入的節點的值小於當前節點,則將當前結點的左孩子作爲新的當前結點
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);// 如果兩個 key 相等,新的 value 覆蓋原有的 value, 然後返回原 value
            } while (t != null);
        }
        else {//沒指定比較器時的處理

            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
       Entry<K,V> e = new Entry<>(key, value, parent);//當沒找到key值相同的節點,則創建新的節點存儲該key值
        if (cmp < 0)
            parent.left = e;// 如果新插入 key 小於 parent 的 key,則 e 作爲 parent 的左子節點
        else
            parent.right = e;// 如果新插入 key 小於 parent 的 key,則 e 作爲 parent 的右子節點
        fixAfterInsertion(e);// 修復紅黑樹,當往TreeMap中插入新的節點之後可能破壞了紅黑樹的性質,所以得調用該函數將其調整爲紅黑樹
        size++;
        modCount++;
        return null;
    }

從上面的代碼可以看到put方法的本質就是構造排序二叉樹的過程,即當往TreeMap中添加一個節點元素時,首先會尋找待插入的位置,如果在尋找的過程中在TreeMap中找到了與待插入節點的key值相同的節點,則替換然後返回該原來的vlaue,如果沒找到,則創建一個新的節點,在恰當的位置處插入該結點,插入完之後會調用fixAfterInsertion(e);來重新修復TreeMap,使其始終滿足紅黑樹的性質。因此可以看到對於相同的key只存在唯一的value值與之對應,因爲原來的會被新的替換。


2get方法

 public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }

 final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)// 如果比較器不爲空,返回getEntryUsingComparator(Object key)的結果
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }
可以看到在get方法中會調用getEntry()方法,getEntry()方法會根據傳入的key值尋找相應的value然後返回,get的過程也包含兩種情況即依據比較器是否爲空分別進行get操作,get尋找的過程事實上與構造二叉排序樹的過程非常相似,代碼也很簡單,在此不做贅述。

3remove方法

 public V remove(Object key) {
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
    }

  private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            if (p.color == BLACK)
                fixAfterDeletion(p);

            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }
可以看到在remove方法中調用了deleteEntry方法,即用來從紅黑樹中刪除某一個節點,在這個過程中同樣會調用fixAfterDeletion(p);方法,即涉及到樹的調整過程。

4clear()方法

public void clear() {
        modCount++;
        size = 0;
        root = null;
    }
代碼非常簡潔,主要就是將size置爲0,同時將根節點root置爲null,這樣就不能通過root訪問其它的節點,這樣GC就會回收該TreeMap的內存空間。

5containsKey(Object key)/containsValue(Object value)

public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }

public boolean containsValue(Object value) {
        for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
            if (valEquals(value, e.value))
                return true;
        return false;
    }
其中containsKey非常簡單,不做贅述,在containsValue(Object value)中可以看到調用了getFirstEntry()方法和successor(e)方法,我們來看一下其源碼:

 final Entry<K,V> getFirstEntry() {
        Entry<K,V> p = root;
        if (p != null)
            while (p.left != null)
                p = p.left;
        return p;
    }

 static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
        if (t == null)
            return null;
        else if (t.right != null) {
            Entry<K,V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
        } else {
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

其中getFirstEntry()方法是用來取整個紅黑樹中的第一個節點,實際是獲取的整棵樹中“最左”的節點,因爲紅黑樹是排序的樹,所以“最左”的節點也是值最小的節點。而successor(e)方法是返回節點e的繼承者,如果e的左孩子非空則返回其左孩子,因此在for循環中配合使用getFirstEntry()方法和successor(Entry<K,V> e)及e!=null是遍歷樹的一種方法。

五總結:

1TreeMap中的元素是排序的,其內部是通過Comparator接口來實現的,可以通過Comparator接口自定義排序規則。

2TreeMap內部是採用紅黑樹Entry來實現的,當使用一個Map集合作爲參數構造一個TreeMap的時候,TreeMap會將Map中的元素先排序,然後排序後的元素put到TreeMap中,put的過程本質上是構造二叉排序樹的過程,插入完之後會調用fixAfterInsertion(e);來重新修復TreeMap,使其始終滿足紅黑樹的性質。

3TreeMap中的元素的key值是唯一的且對於相同的key只存在唯一的value值與之對應,因爲在put的過程中原來的會被新的替換。

4TreeMap不是線程同步的,因爲TreeMap中的方法都未使用synchronized關鍵字修飾,即TreeMap是非同步的。

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