Java從入門到放棄(十四)集合框架之TreeMap原始碼
阿新 • • 發佈:2019-01-11
我們經常需要對一些集合按照指定的規則進行排序,比如學生按照學號排序,或者按照成績排序,集合裡面有專門排序的集合,如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排序。