1. 程式人生 > >Java從入門到放棄(十四)集合框架之TreeMap原始碼

Java從入門到放棄(十四)集合框架之TreeMap原始碼

我們經常需要對一些集合按照指定的規則進行排序,比如學生按照學號排序,或者按照成績排序,集合裡面有專門排序的集合,如TreeMap。TreeMap裡面是使用的紅黑樹結構。

構造方法

   private final Comparator<? super K> comparator;
   private transient Entry<K,V> root; 
   private transient int size = 0;
   public TreeMap() {
        comparator = null;
   }
   public TreeMap
(Comparator<? super K> comparator) { this.comparator = comparator; } public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); } public TreeMap(SortedMap<K, ? extends V> m) { comparator = m.comparator(); try
{ buildFromSorted(m.size(), m.entrySet().iterator(), null, null); } catch (java.io.IOException | ClassNotFoundException cannotHappen) { } }

有三個變數,comparator是指定的比較器,root是紅黑樹的根節點,size是集合內資料的長度。可以看到每一個方法都有指定比較器,在TreeMap中比較器是必須的。因為沒有比較器就沒有辦法對集合內的元素進行對比,也就無法進行排序。

put方法

    public V put(K key, V value) {
        Entry<K,V> t = root;       //根節點
        if (t == null) {
            compare(key, key); // type 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) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);  //使用比較器比較t和key的大小
                if (cmp < 0)
                    t = t.left;     //如果key小於t,把t更改為t的左子樹
                else if (cmp > 0)
                    t = t.right;    //如果key大於t,把t更改為t的右子樹
                else
                    return t.setValue(value);    //如果相等,更改t的值
            } while (t != null);    //一直遍歷到null,找到合適的父節點
        }
        else {
            if (key == null)
                throw new NullPointerException();   //如果比較器和key都是null,丟擲異常
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key; //把key強制轉換為comparable
            do {
                parent = t;
                cmp = k.compareTo(t.key);   //此處和上面是一樣的,就是迴圈找出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節點
        if (cmp < 0)
            parent.left = e;    //左子樹
        else
            parent.right = e;  //右子樹
        fixAfterInsertion(e);   //調整樹的結構
        size++;
        modCount++;
        return null;
    }
 final int compare(Object k1, Object k2) {
        return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
            : comparator.compare((K)k1, (K)k2);
    }

put方法就是從root節點開始查詢合適的父節點,節點是由一個內部類Entry構成的,看一下Entry類的實現

   static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;     //儲存的key值
        V value;   //儲存的value值
        Entry<K,V> left;  //左子樹
        Entry<K,V> right;   //右子樹
        Entry<K,V> parent;   //父節點
        boolean color = BLACK;   //預設黑色

        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;    
            this.value = value;
            this.parent = parent;
        }
        public K getKey() {
            return key;
        }
        public V getValue() {
            return value;
        }

        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裡面包含節點的key-value值,左右節點和父節點。方法也很簡單,只有value的set,get方法,key只有get方法,所以key是不可修改的,重寫了hashcode和eauals以及tostring方法。

remove和getEntry方法

    public V remove(Object key) {
        Entry<K,V> p = getEntry(key);    //獲取對應的節點
        if (p == null)
            return null; 

        V oldValue = p.value;
        deleteEntry(p);    //刪除節點
        return oldValue;   //返回舊的值
    }
    final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key); //如果指定了比較器,執行這個方法,內部和下面的是一樣的
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;  //轉換為comparable
        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;
    }

這裡也是找到對應的節點,然後用deleteEntry方法進行刪除,裡面涉及到紅黑樹的左旋右旋以及顏色的調整,這裡不做解釋。

clear方法

    public void clear() {
        modCount++;
        size = 0;
        root = null;
    }

這裡和之前的集合不一樣,之前的ArrayList等都是遍歷置為null,這裡是直接把root置為null;

程式碼例項

        TreeMap<String,String> treeMap = new TreeMap();
        treeMap.put("China","Beijing");
        treeMap.put("american","Washington");
        treeMap.put("UnitedKingdom","London");
        treeMap.put("Russia","Moscow");
        System.out.println(treeMap);

輸出為:{China=Beijing, Russia=Moscow, UnitedKingdom=London, american=Washington}
這裡就是沒有指定比較器,但是String實現了comparable介面,根據字典順序排序,因為ASCII表中大寫字母的值小於小寫字母的值,所以大寫字母和小寫比較,小寫字母會被認為更大。
如果實體類要進行排序要實現comparable或者compartor介面。具體可參考Java從入門到放棄(四)Comparable 和Comparator排序