【小家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的比較,重新對資料進行排序,儲存的資料是有序的,按序取資料也就不足為奇了。