這篇博客記錄了學習TreeMap時關於put()方法和遍歷TreeMap的解析,方便鞏固學習,僅作參考。
TreeMap<String, String> treeMap = new TreeMap<>() ;
String str1 = "12";
String str2 = "123";
String str3 = "1234";
treeMap.put(str1, "1");
treeMap.put(str2, "2");
treeMap.put(str3, "3");
System.out.println("TreeMap not set iterator");
Iterator<Entry<String, String>> iterator = treeMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator.next();
System.out.println("[" + entry.getKey() + " ," + entry.getValue() + "]");
}
控制檯打印結果:
TreeMap not set iterator
[12 ,1]
[123 ,2]
[1234 ,3]
在解析源碼之前,粗略解釋一下Comparable接口實現:
實現了Comparable接口的類的實例之間是可以相互比較的。在實現Comparable時,會將實例的一個特徵或多個特徵作爲重要判定依據,按照某種方法得出的值(這裏假設爲compareVal),實例間做相互比較。如果實例A的compareVal 大於 實例B的compareVal, A.compareTo(B)返回 1, B.compareTo(A)返回 -1,則對實例的排序是升序的。如果實例A的compareVal 大於 實例B的compareVal, A.compareTo(B)返回 -1, B.compareTo(A)返回 1,則對實例的排序是降序的。
舉一個簡單的例子:
Person類
public class Person implements Comparable{
int age;
String name;
...
// 按照age來比較(升序)
public int compareTo(Person person) {
return this.age - person.age;
}
/**
* // 按照age來比較 (降序)
* public int compareTo(Person person) {
* return person.age - this.age;
* }
*/
}
TreeMap put(K key, V value)源碼解析
public V put(K key, V value) {
Entry<K,V> t = root;
// 第一次向容器中放入元素(k/v)時,創建根節點(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;
// 默認的比較器(comparator)是空的,在創建TreeMap容器時傳入一個比較器。
// 所以按照上面代碼創建TreeMap容器,比較器是空的。
// 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();
// 如果沒有設定比較器,就使用Key對應類的比較方式。所以,下面這行代碼就是得到
// key所實現的比較方式。key先跟父節點(最開始時父節點就是根節點root)的key比較,小於則
// 繼續跟父節點的左子節點比較,大於則跟父節點的右子節點,以此類推,直到找到與e的key相
// 等的節點或者父節點的左子節點或者右子節點爲null。
// 當找到與e的key相等的節點時,替換該節點的value,當父節點的
// 左子節點(右子節點)爲null時,就創建一個節點作爲父節點的左子節點(右子節點)
@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;
}
TreeMap遍歷,源碼解析:
Iterator<Entry<String, String>> iterator = treeMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator.next();
}
treeMap.entrySet().iterator()內部源碼調用順序:
-> TreeMap.entrySet() 返回一個EntrySet的集合
-> new EntrySet()
-> EntrySet.iterator() 返回一個基於EntrySet集合的迭代器EntryIterator
-> new EntryIterator()
-> TreeMap.getFirstEntry() 得到整個樹的最左邊的節點
iterator.next()內部源碼調用順序:
-> EntryIterator.next()
-> PrivateEntryIterator.nextEntry()
-> successor(e) 整個迭代器最主要的迭代代碼在successor(e)中
// 得到Entry的set集合
public Set<Map.Entry<K,V>> entrySet() {
EntrySet es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}
// TreeMap的內部類EntrySet
class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator(getFirstEntry());
}
}
// TreeMap內部類 EntryIterator (迭代器)
// EntryIterator 繼承PrivateEntryIterator類
final class EntryIterator extends PrivateEntryIterator<Map.Entry<K,V>> {
EntryIterator(Entry<K,V> first) {
super(first);
}
public Map.Entry<K,V> next() {
return nextEntry();
}
}
// PrivateEntryIterator
abstract class PrivateEntryIterator<T> implements Iterator<T> {
Entry<K,V> next;
Entry<K,V> lastReturned;
int expectedModCount;
PrivateEntryIterator(Entry<K,V> first) {
expectedModCount = modCount;
lastReturned = null;
next = first; // 初始化EntryIterator時,next指向整個樹的最左邊的葉子節點
}
public final boolean hasNext() {
return next != null; // 當沒有節點時,迭代終止。
}
final Entry<K,V> nextEntry() {
Entry<K,V> e = next; // 第一次調用這個方法時,e指向整個樹的最左邊的葉子節點
if (e == null)
throw new NoSuchElementException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// 整個迭代器最主要的迭代代碼在successor(e)中,successor方法返回下一個節點next,下一次遍
// 歷時,next又用作參數e,作爲基礎節點來尋找下一個返回節點。
next = successor(e);
lastReturned = e;
return e; // 執行successor方法後返回e。
}
}
final Entry<K,V> getFirstEntry() {
Entry<K,V> p = root;
if (p != null)
while (p.left != null)
p = p.left;
return p;
}
TreeMap的getFirstEntry()方法,找到整個樹的最左邊的葉子節點,參考下圖:即節點 D
A
B C
D E F G
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
// 第一次調用時 t 指向整個樹的最左邊的葉子節點 D(如上圖)
if (t == null)
return null;
// 如果節點t有右節點t_right,但是t_right 沒有左節點,就返回t_right(即t的右節點)
// 如果節點t有右節點t_right,並且t_right有左節點t_right_left,就以t_right爲父節點,
// 返回其下最左邊的子節點。
else if (t.right != null) {
Entry<K,V> p = t.right;
while (p.left != null)
p = p.left;
return p;
} else {
// 節點t沒有右節點
// 並且t不是一個右子節點,就返回t的父節點
// 如果節點t是一個右節點,就返回t的父節點的父節點
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p; //注意這裏返回的節點P用作下一次迭代的基礎,而不是iterator.next()的返回值
// iterator.next()返回值是節點 t
}
}
以上圖所示的樹結構圖爲例,以iterator.next()的調用順序爲例:
調用前next指的是調用前next指向的節點
調用後next指的是調用後next指向的節點
次數 | 調用前next | 調用後next | 返回節點 | 分析 |
1 | D | B | D | D節點沒有右節點,但是D節點不是一個右節點,所以調用後next()方法後,next指向D的父節點B。 |
2 | B | E | B | B節點有右節點E,但是E節點沒有左節點,所以調用後next()方法後,next指向B的右節點E。 |
3 | E | A | E | E沒有右節點,並且B節點是一個右節點,所以調用後next()方法後,next指向E的父節點B的父節點A。 |
依次類推,整個樹的遍歷結果是DBEAFCG。
根據TreeMap的遍歷代碼可以看出遍歷的順序是左節點 -> 父節點 -> 右節點,是中序遍歷。
自定義TreeMap的比較器
通過自定義比較器來控制整個樹的節點順序。這裏爲了偷懶,採用與String排序相反的方式。
TreeMap<String, String> treeMapWithIterator = new TreeMap<>((x, y)->{
return y.compareTo(x);
});
treeMapWithIterator.put(str1, "1");
treeMapWithIterator.put(str2, "2");
treeMapWithIterator.put(str3, "3");
System.out.println("降序:");
// TreeMap是用中序遍歷
Iterator<Entry<String, String>> iterator2 = treeMapWithIterator.entrySet().iterator();
while (iterator2.hasNext()) {
Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator2.next();
System.out.println("[" + entry.getKey() + " ," + entry.getValue() + "]");
}
下面是沒有自定義比較器的測試,默認使用String實例的比較方式:
TreeMap<String, String> treeMap = new TreeMap<>() ;
String str1 = "12";
String str2 = "123";
String str3 = "1234";
treeMap.put(str1, "1");
treeMap.put(str2, "2");
treeMap.put(str3, "3");
System.out.println("升序:");
Iterator<Entry<String, String>> iterator = treeMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator.next();
System.out.println("[" + entry.getKey() + " ," + entry.getValue() + "]");
}
看看兩者的輸出結果對比:
升序
[12 ,1]
[123 ,2]
[1234 ,3]
降序:
[1234 ,3]
[123 ,2]
[12 ,1]
在構建TreeMap時如果沒設定比較器,那麼往TreeMap中放入元素時就會使用Key對應的類實現的比較方式。
如果既沒有設定比較器,Key對應的類也未實現Comparable接口,那麼就會出現ClassCastException
參考put()方法的這行代碼: Comparable<? super K> k = (Comparable<? super K>) key
僅作參考,有疏漏或者不正確的地方歡迎指出,相互學習。