1. 程式人生 > >【小家java】SortedMap和NavigableMap的使用介紹---TreeMap的原始碼簡單分析

【小家java】SortedMap和NavigableMap的使用介紹---TreeMap的原始碼簡單分析

相關閱讀

【小家java】java5新特性(簡述十大新特性) 重要一躍
【小家java】java6新特性(簡述十大新特性) 雞肋升級
【小家java】java7新特性(簡述八大新特性) 不溫不火
【小家java】java8新特性(簡述十大新特性) 飽受讚譽
【小家java】java9新特性(簡述十大新特性) 褒貶不一
【小家java】java10新特性(簡述十大新特性) 小步迭代
【小家java】java11新特性(簡述八大新特性) 首個重磅LTS版本


參考閱讀:
【小家java】HashMap原理、TreeMap、ConcurrentHashMap的原理、效能、安全方面大解析-----看這一篇就夠了

SortedMap和NavigableMap

決定在講解TreeMap的原始碼之前,先講解這兩個介面

SortedMap和SortedSet介面兩個介面jdk1.2就已經提供,擴充套件的NavigableMap與NavigableSet介面jdk1.6才開始支援。

SortedMap:顧名思義,此介面應該與排序有關,以下是它的一些方法:

Comparator<? super K> comparator(); //可以自定義排序比較器

//按key升序排列,返回子對映,fromKey到toKey,包括fromKey,不包括toKey
SortedMap<
K,V>
subMap(K fromKey, K toKey); //按key升序排列,返回子對映,開頭到toKey,不包括toKey SortedMap<K,V> headMap(K toKey); //按key升序排列,返回子對映,fromKey到末尾,包括fromKey SortedMap<K,V> tailMap(K fromKey); //按key升序排列,返回第一個key K firstKey(); //按key升序排列,返回最後一個key K lastKey(); //返回key的集合,升序排列 Set<K> keySet(); //返回value的集合,按key升序排列,
Collection<V> values(); //返回Entry的集合,按key升序排列 Set<Map.Entry<K, V>> entrySet();

再看NavigableMap,它繼承了SortedMap:

public interface NavigableMap<K,V> extends SortedMap<K,V>

它自己又定義了一些導航方法:

//返回第一個key小於引數的Entry
Map.Entry<K,V> lowerEntry(K key);
//返回第一個key小於引數的key
K lowerKey(K key);
//返回第一個key小於等於引數的Entry
Map.Entry<K,V> floorEntry(K key);
//返回第一個key小於等於引數的key
K floorKey(K key);
//返回第一個key大於等於引數的Entry
Map.Entry<K,V> ceilingEntry(K key);
//返回第一個key大於等於引數的key
K ceilingKey(K key);
//返回第一個key大於引數的Entry
Map.Entry<K,V> higherEntry(K key);
//返回第一個key大於引數的key
K higherKey(K key);

//返回key最小的Entry
Map.Entry<K,V> firstEntry();
//返回key最大的Entry
Map.Entry<K,V> lastEntry();


//刪除並返回key最小的Entry
Map.Entry<K,V> pollFirstEntry();
//刪除並返回key最大的Entry
Map.Entry<K,V> pollLastEntry();

//返回key降序排列的NavigableMap(檢視)  注意是檢視,所以對它進行一個remove操作,也會影響到原來的Map的  是同一個引用
NavigableMap<K,V> descendingMap();
//返回key升序排列的NavigableSet
NavigableSet<K> navigableKeySet();
//返回key降序排列的NavigableSet
NavigableSet<K> descendingKeySet();

//返回key升序排列的子對映,設定包含標誌
NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive);

//按key升序排列,返回子對映,開頭到toKey,設定包含標誌
NavigableMap<K,V> headMap(K toKey, boolean inclusive);

//按key升序排列,返回子對映,fromKey到末尾,設定包含標誌
NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);

//同時也繼承了SortedMap的【不帶包含標誌】的子對映方法
SortedMap<K,V> subMap(K fromKey, K toKey);
SortedMap<K,V> headMap(K toKey);
SortedMap<K,V> tailMap(K fromKey);

NavigableMap所有已知實現類:ConcurrentSkipListMap(後面博文會有講解), TreeMap

NavigableMap擴充套件了 SortedMap,具有了針對給定搜尋目標返回最接近匹配項的導航方法。
方法 lowerEntry、floorEntry、ceilingEntry 和 higherEntry 分別返回與小於、小於等於、大於等於、大於給定鍵的鍵關聯的 Map.Entry 物件,如果不存在這樣的鍵,則返回 null。
類似地,方法 lowerKey、floorKey、ceilingKey 和 higherKey 只返回關聯的鍵。所有這些方法是為查詢條目而不是遍歷條目而設計的

descendingMap 方法返回對映的一個檢視,該視圖表示的所有關係方法和方向方法都是逆向的。升序操作和檢視的效能很可能比降序操作和檢視的效能要好。

此介面還定義了 firstEntry、pollFirstEntry、lastEntry 和 pollLastEntry 方法,它們返回和/或移除最小和最大的對映關係(如果存在),否則返回 null。

    public static void main(String[] args) {

        // NavigableMap多型接收TreeMap的例項
        NavigableMap<String, Integer> navigatorTreeMap = new TreeMap<String, Integer>() {{
            put("aa", 11);
            put("bb", 22);
            put("cc", 33);
            put("dd", 44);
            put("ee", 55);
            put("ff", 55);
            put("gg", 55);
        }};


        System.out.println(navigatorTreeMap.size());// 7個元素:7
        System.out.println(navigatorTreeMap.ceilingKey("cc"));// 返回大於等於cc的最小鍵:cc
        System.out.println(navigatorTreeMap.ceilingEntry("c"));//  返回一個鍵-值對映關係,它與大於等於cc的最小鍵關聯:cc=33
        System.out.println(navigatorTreeMap.firstKey());// 最小鍵:aa
        System.out.println(navigatorTreeMap.firstEntry());// 最小鍵對應的k-v鍵值對:aa=11


        System.out.println(navigatorTreeMap.floorEntry("c"));// 返回一個鍵-值對映關係,它與小於等於給定鍵的最大鍵關聯:bb=22
        System.out.println(navigatorTreeMap.floorKey("cc"));//   返回小於等於cc的最大鍵:cc
        System.out.println(navigatorTreeMap.headMap("bb"));// 返回此對映的部分檢視,其鍵值嚴格小於bb:{aa=11}
        System.out.println(navigatorTreeMap.headMap("bb", true));// 同上小於等於(true):{aa=11, bb=22}
        System.out.println(navigatorTreeMap.higherEntry("c"));// 返回一個鍵-值對映關係,它與小於等於給定鍵的最大鍵關聯:cc=33
        System.out.println(navigatorTreeMap.higherKey("cc"));//   返回小於等於cc的最大鍵:dd
        System.out.println(navigatorTreeMap.lastEntry());// 返回一個鍵-值對映關係,它與小於等於給定鍵的最大鍵關聯:gg=55
        System.out.println(navigatorTreeMap.lastKey());//   返回小於等於cc的最大鍵:gg
        System.out.println(navigatorTreeMap.lowerEntry("c"));// 返回一個鍵-值對映關係,它與小於等於給定鍵的最大鍵關聯:bb=22
        System.out.println(navigatorTreeMap.lowerKey("cc"));//    返回嚴格小於cc的最大鍵:bb
        System.out.println(navigatorTreeMap.pollFirstEntry());//  移除並返回與此對映中的最小鍵關聯的鍵-值對映關係:aa=11
        System.out.println(navigatorTreeMap.pollLastEntry());//  移除並返回與此對映中的最大鍵關聯的鍵-值對映關係:gg=55
        System.out.println(navigatorTreeMap.navigableKeySet());//   返回此對映中所包含鍵的
        // NavigableSet 檢視。:[bb, cc, dd, ee, ff]

        System.out.println(navigatorTreeMap.subMap("aa", true, "dd", true));// 返回部分檢視,true表示包括當前元素鍵值對:{bb=22, cc=33, dd=44}
        System.out.println(navigatorTreeMap.subMap("bb", "dd"));// 返回部分檢視包括前面的元素,不包括後面的:{bb=22, cc=33}
        System.out.println(navigatorTreeMap.tailMap("cc"));// 返回元素大於cc的元素對映檢視,包括cc://{cc=33, dd=44, ee=55, ff=55}
        System.out.println(navigatorTreeMap.tailMap("cc", false));// 返回元素大於等於cc的元素對映檢視:{dd=44, ee=55, ff=55}

        //逆序檢視
        NavigableMap<String, Integer> descendingMap = navigatorTreeMap.descendingMap();
        System.out.println(navigatorTreeMap); //原來的Map:
        System.out.println(descendingMap);// 返回逆序檢視:{gg=55, ff=55, ee=55, dd=44, cc=33, bb=22, aa=11}
        //執行一個移除操作後  再看看會不會影響到原來的Map
        descendingMap.remove("gg");
        System.out.println(navigatorTreeMap); //原來的Map:{aa=11, bb=22, cc=33, dd=44, ee=55, ff=55}
        System.out.println(descendingMap);// 返回逆序檢視:{ff=55, ee=55, dd=44, cc=33, bb=22, aa=11}
    }

之所以可以去到第一個最後一個元素,或者某個元素的前一個,後一個,是因為集合內部的元素是有序的。

TreeMap 簡介

TreeMap 是一個有序的key-value集合,它是通過紅黑樹實現的。

// Red-black mechanics
private static final boolean RED   = false;
private static final boolean BLACK = true;

// Entry內部類
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;
}

從上面原始碼變數的命名中,很容易看出內部實現原理:紅黑樹。

TreeMap實現了NavigableMap、SortedMap介面(這另個介面下面會有詳細介紹) 意味著它支援一系列的導航方法。比如返回有序的key集合。

TreeMap基於紅黑樹(Red-Black tree)實現。該對映根據其鍵的自然順序進行排序,或者根據建立對映時提供的 Comparator 進行排序,具體取決於使用的構造方法。

TreeMap的基本操作 containsKey、get、put 和 remove 的時間複雜度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。

TreeMap與Map關係如下圖:

在這裡插入圖片描述
(01) TreeMap實現繼承於AbstractMap,並且實現了NavigableMap介面。
(02) TreeMap的本質是R-B Tree(紅黑樹),它包含幾個重要的成員變數: root, size, comparator。

  • root 是紅黑數的根節點。它是Entry型別,Entry是紅黑數的節點,它包含了紅黑數的6個基本組成成分:key(鍵)、value(值)、left(左孩子)、right(右孩子)、parent(父節點)、color(顏色)。Entry節點根據key進行排序,Entry節點包含的內容為value。
  • 紅黑數排序時,根據Entry中的key進行排序;Entry中的key比較大小是根據比較器comparator來進行判斷的。
  • size是紅黑數中節點的個數。

最後簡單看看put方法的原始碼

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) {
            do {
                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);
            } 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);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

每次插入一個數據都會利用比較函式進行key的比較,重新對資料進行排序,儲存的資料是有序的,按序取資料也就不足為奇了。