TreeMap實現原理

在用TreeMap以前咱們要對TreeMap有個總體的認識。java

一、TreeMap介紹

TreeMap是一個經過紅黑樹實現有序的key-value集合。node

TreeMap繼承AbstractMap,也即實現了Map,它是一個Map集合算法

TreeMap實現了NavigableMap接口,它支持一系列的導航方法,緩存

TreeMap實現了Cloneable接口,它能夠被克隆數據結構

TreeMap introduction:A Red-Black tree based NavigableMap implementation. The map is sorted according to the natural ordering of its keys, or by a Comparator provided at map creation time, depending on which constructor is used.
This implementation provides guaranteed log(n) time cost for the containsKey, get, put and remove operations. Algorithms are adaptations of those in Cormen, Leiserson, and Rivest’s Introduction to Algorithms.

TreeMap基於紅黑樹(Red-Black tree)實現。映射根據其鍵的天然順序進行排序,或者根據建立映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。TreeMap的基本操做containsKey、get、put、remove方法,它的時間複雜度是log(n)。--(翻譯JDK中關於TeeMap簡介)app

TreeMap是非同步的。ide

TreeMap與Map的關係圖以下:函數

TreeMap本質是Red-Black Tree,它包含幾個重要的成員變量:root、size、comparator。其中root是紅黑樹的根節點。它是Entry類型,Entry是紅黑樹的節點,它包含了紅黑樹的6個基本組成:key、value、left、right、parent和color。Entry節點根據根據Key排序,包含的內容是value。Entry中key比較大小是根據比較器comparator來進行判斷的。size是紅黑樹的節點個數。具體的紅黑樹算法,請自行百度。性能

二、TreeMap源碼解析(JDK1.6版本)

爲了更瞭解TreeMap的原理,下面咱們將根據TreeMap中實現的方法來對源碼作一個詳細的說明。ui

2.一、TreeMap數據結構

    TreeMap的定義以下:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable

 TreeMap繼承AbstractMap,實現NavigableMap、Cloneable、Serializable三個接口。其中AbstractMap代表TreeMap爲一個Map即支持key-value的集合, NavigableMap則意味着它支持一系列的導航方法,具有針對給定搜索目標返回最接近匹配項的導航方法 。

    TreeMap中同時包含以下幾個重要的屬性:

/**
     * The comparator used to maintain order in this tree map, or
     * null if it uses the natural ordering of its keys.
     * 比較器,用來給TreeMap排序
     * @serial
     */
    private final Comparator<? super K> comparator;
	/**
	 * 紅黑樹的根節點
	 */
    private transient Entry<K,V> root = null;

    /**
     * The number of entries in the tree
	 * 紅黑樹的節點總數
     */
    private transient int size = 0;

    /**
     * The number of structural modifications to the tree.
	 * 紅黑樹的修改次數
     */
    private transient int modCount = 0;

    // Red-black mechanics
    /**
     * 紅黑樹的顏色--紅色
     */
    private static final boolean RED   = false;

    /**
     * 紅黑樹的顏色--黑色
     */
    private static final boolean BLACK = true;

    對於葉子節點Entry來講,Entry是TreeMap的內部類,它有幾個重要屬性:

static final class Entry<K,V> implements Map.Entry<K,V> {
	//鍵
	K key;
	//值
        V value;
	//左孩子
        Entry<K,V> left = null;
	//右孩子
        Entry<K,V> right = null;
	//父親
        Entry<K,V> parent;
	//顏色
        boolean color = BLACK;

        /**
         * Make a new cell with given key, value, and parent, and with
         * <tt>null</tt> 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;
        }
    }

2.二、TreeMap的構造器

    2.2.一、默認構造器

        使用默認構造器構造TreeMap時,使用java的默認的比較器比較Key的大小,從而對TreeMap進行排序

/**
     * Constructs a new, empty tree map, using the natural ordering of its keys. 
     * 默認構造器
     */
    public TreeMap() {
        comparator = null;
    }

    2.2.二、帶比較器的構造函數

/**
     * Constructs a new, empty tree map, ordered according to the given comparator. 
     * 給定比較器的構造函數
     */
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    2.2.三、帶Map的構造函數,Map會成爲TreeMap的子集

/**
     * Constructs a new tree map containing the same mappings as the given
     * map
     */
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

    該構造函數會調用putAll()將m中的全部元素添加到TreeMap中。putAll()源碼以下 :

/**
     * Copies all of the mappings from the specified map to this map.
     * These mappings replace any mappings that this map had for any
     * of the keys currently in the specified map.
	 * 將Map中的節點所有添加到TreeMap中
     */
    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);
    }

    2.2.四、帶SortedMap的構造函數,SortedMap會成爲TreeMap的子集

/**
     * Constructs a new tree map containing the same mappings and
     * using the same ordering as the specified sorted map.  This
     * method runs in linear time.
     */
    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參數的構造器都調用了buildFromSorted()。buildFromSorted()設計代碼以下:

// 根據已經一個排好序的map建立一個TreeMap
    // 將map中的元素逐個添加到TreeMap中,並返回map的中間元素做爲根節點。
    private final Entry<K,V> buildFromSorted(int level, int lo, int hi,
					     int redLevel,
					     Iterator it,
					     java.io.ObjectInputStream str,
					     V defaultVal)
        throws  java.io.IOException, ClassNotFoundException {
        /*
         * Strategy: The root is the middlemost element. To get to it, we
         * have to first recursively construct the entire left subtree,
         * so as to grab all of its elements. We can then proceed with right
         * subtree.
         *
         * The lo and hi arguments are the minimum and maximum
         * indices to pull out of the iterator or stream for current subtree.
         * They are not actually indexed, we just proceed sequentially,
         * ensuring that items are extracted in corresponding order.
         */

        if (hi < lo) return null;

        int mid = (lo + hi) / 2;

        Entry<K,V> left  = null;
        if (lo < mid)
            left = buildFromSorted(level+1, lo, mid - 1, redLevel,
				   it, str, defaultVal);

        // extract key and/or value from iterator or stream
        K key;
        V value;
        if (it != null) {
            if (defaultVal==null) {
                Map.Entry<K,V> entry = (Map.Entry<K,V>)it.next();
                key = entry.getKey();
                value = entry.getValue();
            } else {
                key = (K)it.next();
                value = defaultVal;
            }
        } else { // use stream
            key = (K) str.readObject();
            value = (defaultVal != null ? defaultVal : (V) str.readObject());
        }

        Entry<K,V> middle =  new Entry<K,V>(key, value, null);

        // color nodes in non-full bottommost level red
        if (level == redLevel)
            middle.color = RED;

        if (left != null) {
            middle.left = left;
            left.parent = middle;
        }

        if (mid < hi) {
            Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
					       it, str, defaultVal);
            middle.right = right;
            right.parent = middle;
        }

        return middle;
    }

對buildFromSorted方法解讀   

  • buildFromSorted是經過遞歸將SortedMap中的元素逐個關聯
  • buildFromSorted返回middle節點做爲root
  • buildFromSorted添加到紅黑樹中時,只將level == redLevel的節點設爲紅色。

2.三、TreeMap實現的Serializable接口

    TreeMap實現了java.io.Serializable,分別實現了串行讀取、寫入功能。串行寫入函數是writeObject(),它的做用是將TreeMap的容量,全部的Entry都寫入到輸出流。而串行讀取函數是readObject(),它的做用是將TreeMap的容量,全部的Entry依次讀出。經過readObject和writeObject可以幫助咱們實現TreeMap的串行傳輸。

private static final long serialVersionUID = 919286545866124006L;

    /**
     * Save the state of the <tt>TreeMap</tt> instance to a stream (i.e.,
     * serialize it).
     *
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out the Comparator and any hidden stuff
        s.defaultWriteObject();

        // Write out size (number of Mappings)
        s.writeInt(size);

        // Write out keys and values (alternating)
        for (Iterator<Map.Entry<K,V>> i = entrySet().iterator(); i.hasNext(); ) {
            Map.Entry<K,V> e = i.next();
            s.writeObject(e.getKey());
            s.writeObject(e.getValue());
        }
    }

    /**
     * Reconstitute the <tt>TreeMap</tt> instance from a stream (i.e.,
     * deserialize it).
     */
    private void readObject(final java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in the Comparator and any hidden stuff
        s.defaultReadObject();

        // Read in size
        int size = s.readInt();

        buildFromSorted(size, null, s, null);
    }

2.四、TreeMap實現了Cloneable接口

    TreeMap實現了Cloneable接口,即實現了clone()方法。clone()方法的做用很簡單,就是克隆一個TreeMap對象並返回。

/**
     * Returns a shallow copy of this <tt>TreeMap</tt> instance. (The keys and
     * values themselves are not cloned.)
     *
     * @return a shallow copy of this map
     */
    public Object clone() {
        TreeMap<K,V> clone = null;
        try {
            clone = (TreeMap<K,V>) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError();
        }

        // Put clone into "virgin" state (except for comparator)
        clone.root = null;
        clone.size = 0;
        clone.modCount = 0;
        clone.entrySet = null;
        clone.navigableKeySet = null;
        clone.descendingMap = null;

        // Initialize clone with our mappings
        try {
            clone.buildFromSorted(size, entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }

        return clone;
    }

2.五、TreeMap重要方法的實現原理

    TreeMap繼承AbstractMap,也便是一個Map。這裏對map的主要方法作一個解析

    2.5.一、TreeMap的put()方法實現原理

/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     */
    public V put(K key, V value) {
	//用t表示二叉樹的當前節點
        Entry<K,V> t = root;
		//t爲null表示一個空樹,即TreeMap中沒有任何元素,直接插入
        if (t == null) {
		//將新的key-value鍵值對建立爲一個Entry節點,並將該節點賦予給root
            root = new Entry<K,V>(key, value, null);
            size = 1;
			//修改次數加1
            modCount++;
            return null;
        }
		//cmp表示key排序的返回結果
        int cmp;
        Entry<K,V> parent;
        // 指定的排序算法
        Comparator<? super K> cpr = comparator;
		//若是cpr不爲空,則採用給定的排序算法建立TreeMap集合
        if (cpr != null) {
            do {
                parent = t;//parent指向上次循環後的t
                cmp = cpr.compare(key, t.key);
				//cmp返回值小於0,表示新增節點的key小於當前key的值,則以當前節點的左孩子做爲新的當前節點
				//不然,以當前節點的右孩子做爲新的當前節點
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
		//若是cpr爲空,則採用默認的排序算法進行建立TreeMap集合
        else {
            if (key == null)
                throw new NullPointerException();
            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<K,V>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
		//上面已經完成了排序二叉樹的構建,將新增節點插入該樹中的合適位置,下面fixAfterInsertion方法就是對這棵樹進行調整、平衡
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

    上述代碼中do代碼塊實現排序二叉樹的核心算法,經過該算法咱們能夠確認新增節點在該樹的正確位置。找到該位置後將插入便可,這樣作其實尚未完成,由於咱們知道TreeMap的底層實現是紅黑樹,紅黑樹是一個平衡排序二叉樹,普通的排序二叉樹可能會出現失衡的狀況,因此下一步就是要進行調整。fixAfterInsertion(e); 調整的過程務必會涉及到紅黑樹的左旋、右旋、着色三個基本操做。代碼以下:

/** From CLR */
    private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;

        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

    這段代碼中包含了左旋(rotateLeft())、右旋(rotateRight())和着色(setColor)等符合紅黑樹新增節點的處理過程。

    2.5.二、TreeMap的remove()方法實現原理

/**
     * Removes the mapping for this key from this TreeMap if present.
     *
     */
    public V remove(Object key) {
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

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

    經過這段代碼能夠看出,TreeMap的remove()方法中執行刪除的真正方式是deleteEntry()方法。deleteEntry()代碼以下:

/**
     * Delete node p, and then rebalance the tree.
     */
    private void deleteEntry(Entry<K,V> p) {
        modCount++;//修改次數 +1;
        size--;//元素個數 -1

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
		/*
         * 被刪除節點的左子樹和右子樹都不爲空,那麼就用 p節點的中序後繼節點代替 p 節點
         * successor(P)方法爲尋找P的替代節點。規則是右分支最左邊,或者 左分支最右邊的節點
         * ---------------------(1)
         */
        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.
		//replacement爲替代節點,若是P的左子樹存在那麼就用左子樹替代,不然用右子樹替代
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);
		/*
         * 刪除節點,分爲上面提到的三種狀況
         * -----------------------(2)
         */
        //若是替代節點不爲空
        if (replacement != null) {
            // Link replacement to parent
			//replacement來替代P節點
            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;
            }
        }
    }

 三、TreeMap的使用場景

    咱們瞭解了什麼是TreeMap以及它的部分實現原理。那咱們該如何運用treeMap呢?TreeMap是Map接口的具體實現,它的應用場景與Map應用場景大致相同。Map用於保存具備"映射關係"的數據,所以Map集合裏保存着兩組值,一組值用於保存Map裏的key,另一組值用於保存Map裏的value。key和value均可以是任何引用類型的數據。Map的key不容許重複,即同一個Map對象的任何兩個key經過equals方法比較結果老是返回false。 關於Map,咱們要從代碼複用的角度去理解,java是先實現了Map,而後經過包裝了一個全部value都爲null的Map就實現了Set集合 Map的這些實現類和子接口中key集的存儲形式和Set集合徹底相同(即key不能重複) Map的這些實現類和子接口中value集的存儲形式和List很是相似(即value能夠重複、根據索引來查找) 。TreeMap一般比HashMap、Hashtable要慢(尤爲是在插入、刪除key-value對時更慢),由於TreeMap底層採用紅黑樹來管理鍵值對。可是TreeMap有一個好處就是:TreeMap中的key-value對老是處於有序狀態,無須專門進行排序操做。而且雖然TreeMap在插入和刪除方面性能比較差,可是在分類處理的時候做用很大,遍歷的速度很快。TreeMap的使用示例

/**
	 * 該方法用於得到初始化成員變量sysbasecodeCache,調用remote接口,
	 * 得到全部的cbossBaseCode,並按code_type分
	 * 類,每一類編碼存放在一個TreeMap中,並把全部生成的TreeMap做 爲m_SysBaseTable的成員保存。
	 * 
	 * @return void
	 * @throws
	 */
	public static void initSysBaseCode() throws CBossDataAccessException {
		long start = System.currentTimeMillis();
		CBossLogUtil.getLogger(BaseCodeHelper.class).info(">>開始緩存["+CbossBaseCodeDataModule.TABLE_NAME+"]數據...");
		// 初始化前先清理緩存
		cleancache();
		int iCodeType = -999;
		Map<String, CbossBaseCodeDataModule> treeMap = null;
		Connection conn = null;
		try {
			conn = DataSourceProxy.getConnectionBySchema(CbossBaseCodeDataModule.SCHEMA_NAME);
			CbossBaseCodeDAO basedao = new CbossBaseCodeDAO(conn);
			List<CbossBaseCodeDataModule> baseCodes = basedao.selectAll();
			for (CbossBaseCodeDataModule cbossBaseCode : baseCodes) {
				iCodeType = cbossBaseCode.getCodeType();
				treeMap = (TreeMap) sysbasecodeCache.get(new Integer(iCodeType));
				if (treeMap == null) {
					treeMap = new TreeMap<String, CbossBaseCodeDataModule>();
					sysbasecodeCache.put(new Integer(iCodeType), treeMap);
				}
				treeMap.put(cbossBaseCode.getCodeId(), cbossBaseCode);
			}
		} catch (Exception e) {
			throw new CBossDataAccessException(e.getMessage() + "基礎代碼初始化時出現異常");
		} finally {
			DataSourceProxy.closeConnection(conn);
			CBossLogUtil.getLogger(BaseCodeHelper.class).info(">>["+CbossBaseCodeDataModule.TABLE_NAME+"]數據緩存完成, cost("+(System.currentTimeMillis() - start)+")ms");
		}
	}

    在遍歷TreeMap的時候,可以直接肯定分類,而後在分類中遍歷,最終可以以最快的速度獲取想要的結果。

相關文章
相關標籤/搜索