java集合(6):TreeMap原始碼分析(jdk1.8)
前言
TreeMap的基本概念:
TreeMap集合是基於紅黑樹(Red-Black tree)的 NavigableMap實現。該集合最重要的特點就是可排序,該對映根據其鍵的自然順序進行排序,或者根據建立對映時提供的 Comparator 進行排序,具體取決於使用的構造方法。這句話是什麼意思呢?就是說TreeMap可以對新增進來的元素進行排序,可以按照預設的排序方式,也可以自己指定排序方式。
根據上一條,我們要想使用TreeMap儲存並排序我們自定義的類(如User類),那麼必須自己定義比較機制:一種方式是User類去實現java.lang.Comparable介面,並實現其compareTo()方法。另一種方式是寫一個類(如MyCompatator)去實現java.util.Comparator介面,並實現compare()方法,然後將MyCompatator類例項物件作為TreeMap的構造方法引數進行傳參(當然也可以使用匿名內部類),這些比較方法是怎麼被呼叫的將在原始碼中講解。
下圖是Map集合體系類圖。
正文
TreeMap原始碼分析
1,類名及類成員變數
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
// 比較器物件
private final Comparator<? super K> comparator;
// 根節點
private transient Entry<K,V> root;
// 集合大小
private transient int size = 0;
// 樹結構被修改的次數
private transient int modCount = 0;
// 靜態內部類用來表示節點型別
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; //
}
}
2,類構造方法
public TreeMap() { // 1,無參構造方法
comparator = null; // 預設比較機制
}
public TreeMap(Comparator<? super K> comparator) { // 2,自定義比較器的構造方法
this.comparator = comparator;
}
public TreeMap(Map<? extends K, ? extends V> m) { // 3,構造已知Map物件為TreeMap
comparator = null; // 預設比較機制
putAll(m);
}
public TreeMap(SortedMap<K, ? extends V> m) { // 4,構造已知的SortedMap物件為TreeMap
comparator = m.comparator(); // 使用已知物件的構造器
try {
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
}
3,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; // 集合大小為1
modCount++; // 結構修改次數自增
return null;
}
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator; // 比較器物件
// 如果比較器物件不為空,也就是自定義了比較器
if (cpr != null) {
do { // 迴圈比較並確定元素應插入的位置(也就是找到該元素的父節點)
parent = t; // t就是root
// 呼叫比較器物件的compare()方法,該方法返回一個整數
cmp = cpr.compare(key, t.key);
if (cmp < 0) // 待插入元素的key"小於"當前位置元素的key,則查詢左子樹
t = t.left;
else if (cmp > 0) // 待插入元素的key"大於"當前位置元素的key,則查詢右子樹
t = t.right;
else // "相等"則替換其value。
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) // 待插入元素的key"小於"當前位置元素的key,則查詢左子樹
t = t.left;
else if (cmp > 0) // 待插入元素的key"大於"當前位置元素的key,則查詢右子樹
t = t.right;
else // "相等"則替換其value。
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++; // 集合大小+1
modCount++; // 集合結構被修改次數+1
return null;
}
3.1,自定義比較器的使用。說了這麼多關於比較器的內容,不上手試試這麼能行?
- 先來看下面這段程式碼
package com.jimmy.map;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
public class TreeMapDemo2 {
public static void main(String[] args) {
Map<String, String> map = new TreeMap<>();
map.put("ddd", "444");
map.put("ccc", "333");
map.put("bbb", "222");
map.put("aaa", "111");
Set<Entry<String, String>> entrySet = map.entrySet();
for (Entry<String, String> each : entrySet) {
System.out.println(each.getKey()+"::"+each.getValue());
}
}
}
輸出結果如下,結果是排序過的,為什麼呢?那是因為String類實現了Comparable介面並實現了compareTo()方法,該方法按字典順序比較兩個字串,請自行檢視其實現。
aaa::111
bbb::222
ccc::333
ddd::444
- 下面我們寫個自定義User類,使用2種方式將類物件按照age欄位從小到大排序。
方式1,User實現Comparable介面並實現了compareTo()方法
User類
package com.jimmy.domain;
public class User implements Comparable<User>{
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
@Override
public String toString() {
return "User [username=" + username + ", age=" + age + "]";
}
@Override
public int compareTo(User user) {
int temp = this.age - user.age;
return temp == 0 ? this.username.compareTo(user.username) : temp;
}
}
測試程式碼
package com.jimmy.map;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import com.jimmy.domain.User;
public class TreeMapDemo1 {
public static void main(String[] args) {
Map<User, String> map = new TreeMap<>();
map.put(new User("jimmy1", 30), "hello");
map.put(new User("jimmy2", 30), "hello");
map.put(new User("jimmy", 22), "hello");
map.put(new User("jimmy", 20), "hello");
Set<Entry<User, String>> entrySet = map.entrySet();
for (Entry<User, String> each : entrySet) {
System.out.println(each.getKey()+"::"+each.getValue());
}
}
}
輸出結果如下,首先按age排序,若年齡相等則再按username的字母表順序排序。
User [username=jimmy, age=20]::hello
User [username=jimmy, age=22]::hello
User [username=jimmy1, age=30]::hello
User [username=jimmy2, age=30]::hello
方式2,寫一個類實現java.util.Comparator介面,並將該類物件傳遞給TreeMap的構造方法。這種方式將實體類和比較機制解耦合,可以寫很多個不同的比較器物件。
實體類
package com.jimmy.domain;
public class User3 { // User物件不再實現任何介面
private String username;
private int age;
public User3(String username, int age) {
super();
this.username = username;
this.age = age;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User3 [username=" + username + ", age=" + age + "]";
}
}
比較器類
package com.jimmy.map;
import java.util.Comparator;
import com.jimmy.domain.User3;
public class TreeMapComparator implements Comparator<User3>{ // 比較器類
@Override
public int compare(User3 o1, User3 o2) {
int temp = o1.getAge() - o2.getAge();
return temp == 0 ? o1.getUsername().compareTo(o2.getUsername()) : temp;
}
}
測試程式碼
package com.jimmy.map;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import com.jimmy.domain.User3;
public class TreeMapDemo3 {
public static void main(String[] args) {
Map<User3, String> map = new TreeMap<>(new TreeMapComparator());
map.put(new User3("jimmy1", 30), "hello");
map.put(new User3("jimmy2", 30), "hello");
map.put(new User3("jimmy", 22), "hello");
map.put(new User3("jimmy", 20), "hello");
Set<Entry<User3, String>> entrySet = map.entrySet();
for (Entry<User3, String> each : entrySet) {
System.out.println(each.getKey()+"::"+each.getValue());
}
}
}
輸出結果如下,跟上面的相同。
User3 [username=jimmy, age=20]::hello
User3 [username=jimmy, age=22]::hello
User3 [username=jimmy1, age=30]::hello
User3 [username=jimmy2, age=30]::hello
當然,我們還可以不寫比較器類,而是使用匿名內部類的形式來寫比較器。
package com.jimmy.map;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import com.jimmy.domain.User3;
public class TreeMapDemo4 {
public static void main(String[] args) {
Map<User3, String> map = new TreeMap<>(new Comparator<User3>() {
@Override
public int compare(User3 o1, User3 o2) {
int temp = o1.getAge() - o2.getAge();
return temp == 0 ? o1.getUsername().compareTo(o2.getUsername()) : temp;
}
});
map.put(new User3("jimmy1", 30), "hello");
map.put(new User3("jimmy2", 30), "hello");
map.put(new User3("jimmy", 22), "hello");
map.put(new User3("jimmy", 20), "hello");
Set<Entry<User3, String>> entrySet = map.entrySet();
for (Entry<User3, String> each : entrySet) {
System.out.println(each.getKey()+"::"+each.getValue());
}
}
}
4,一幫以getEntry()方法為基礎的獲取元素的方法,其中包括containsKey(),get(),remove()等。
final Entry<K,V> getEntry(Object key) {
// 如果有自定義比較器物件,就按照自定義規則遍歷二叉樹
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
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;
}
5,一幫以getFirstEntry(),getLastEntry()為基礎的獲取頭和尾元素的方法,其中包括:firstKey(),lastKey();firstEntry(),lastEntry();pollFirstEntry(),pollLastEntry();
final Entry<K,V> getFirstEntry() { // 獲取第一個元素也就是最小的元素,一直遍歷左子樹
Entry<K,V> p = root;
if (p != null)
while (p.left != null)
p = p.left;
return p;
}
final Entry<K,V> getLastEntry() { // 獲取最後個元素也就是最大的元素,一直遍歷右子樹
Entry<K,V> p = root;
if (p != null)
while (p.right != null)
p = p.right;
return p;
}
6,keySet()和entrySet()方法,在將HashMap的時候已經講過了,Map沒有迭代器,要將Map轉化為Set,用Set的迭代器才能進行元素迭代。
總結
TreeMap繼承了Map的性質,同時其樹結構又可以進行元素排序,用處很大。