前言
- TreeMap實現了SotredMap接口,它是有序的集合。而且由紅黑樹實現的,每個key-value都作爲一個紅黑樹的節點。如果在調用TreeMap的構造函數時沒有指定比較器,則根據key執行自然排序。
- 紅黑樹:
紅黑樹是二叉樹的一種優化,保留了二叉樹的特性包含一個根節點和兩個子節點,其中 【左子節點 < 根節點 < 右子節點】,又增加了每個節點的顏色紅和黑,通過二叉樹的左右旋轉保證其接近最優二叉樹。想要了解更多紅黑樹的知識,可以看一下下面的博文:- 教你初步瞭解紅黑樹: https://blog.csdn.net/v_JULY_v/article/details/6105630
- 紅黑樹算法的實現與剖析: https://blog.csdn.net/v_JULY_v/article/details/6109153
- 紅黑樹模擬網站: https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
- 根據紅黑樹模擬網站,生成的一個簡單的紅黑樹示意圖:
TreeMap簡單示例:
public class Test {
public static void main(String[] args) throws ParseException {
Map treeMap = new TreeMap<Integer,String>();
treeMap.put(1,"11");
treeMap.put(2,"22");
treeMap.put(3,"33");
treeMap.put(4,"44");
Iterator iter = treeMap.entrySet().iterator();
while(iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
System.out.println("key = "+entry.getKey()+ ";value = " + entry.getValue());
}
}
}
打印結果:
key = 1;value = 11
key = 2;value = 22
key = 3;value = 33
key = 4;value = 44
TreeMap參數定義 和 初始化方法:
-
我們先開看一下 TreeMap 參數定義, 比較重要的有有三個:
comparator: 對比器,對比key之間的大小。
root:根節點,整個紅黑樹的根,例如上面的圖 d 節點就是根節點
size:集合大小 -
TreeMap 的初始化方法也有三個:
TreeMap():其中comparator=null,在實際運用中會取 key 中定義的 comparator ,例如String類型中就內置了 CaseInsensitiveComparator 作爲 Comparator 的實現類。
TreeMap(Comparator<? super K> comparator):帶有對比器的初始化方法。
TreeMap(SortedMap<K, ? extends V> m): 帶有集合m 的初始化方法,沿用m的對比器, 並將m中鍵值對放入到TreeMap中
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 java.util.TreeMap.Entry<K,V> root;
// 集合大小
private transient int size = 0;
// 對樹進行結構修改的次數。
private transient int modCount = 0;
// 初始化
public TreeMap() {
comparator = null;
}
// 帶有 對比器 的初始化
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
// 初始化 並將 m 全部放置到 TreeMap 中
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
.....
}
TreeMap 中保存 key-value 的 Entry
- Entry 中包含了 key、value、left、right、parent、color
left:表示當前節點的左子節點
right:表示當前節點的右子節點
parent: 自己的父節點 - 代碼如下:
// 鍵值對
static final class Entry<K,V> implements Map.Entry<K,V> {
K key; // key 值
V value; // value 值
java.util.TreeMap.Entry<K,V> left; // 左邊子的節點
java.util.TreeMap.Entry<K,V> right; // 右邊子的節點
java.util.TreeMap.Entry<K,V> parent; // 父節點
boolean color = BLACK; // 默認節點顏色 黑
// 初始化 Entry
Entry(K key, V value, java.util.TreeMap.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;
}
}
put 方法
TreeMap 中定義了很多方法,其中最關鍵的是 put(K key, V value) 方法,該方法將 key-value 包裝成了一個 Entry,並通過 Comparator 在紅黑樹中找到指定的位置將其放置其中,最後通過 fixAfterInsertion 實現紅黑樹的修復,完成新增操作,下面看源碼:
// 添加一個數據
public V put(K key, V value) {
// 獲取到紅黑樹的根節點
java.util.TreeMap.Entry<K,V> t = root;
// 根節點爲null, TreeMap中沒有任何數據
if (t == null) {
// 類型檢查(可能爲空)
compare(key, key);
// 將當前的 key,value 生成根節點Entry
root = new java.util.TreeMap.Entry<>(key, value, null);
size = 1;
// 操作次數 +1
modCount++;
return null;
}
int cmp;
// 定義父節點
java.util.TreeMap.Entry<K,V> parent;
// 比較器
Comparator<? super K> cpr = comparator;
// 用戶傳入的比較器不爲空
if (cpr != null) {
// 循環查找 key在二叉樹對應的位置【即找到一個父節點】
do {
parent = t;
// key與當前節點的key進行對比
cmp = cpr.compare(key, t.key);
// cmp < 0 找到當前節點的左子節點再去對比
if (cmp < 0)
t = t.left;
// cmp > 0 找到當前節點的右子節點再去對比
else if (cmp > 0)
t = t.right;
// cmp = 0 當前節點即是key的節點,用Value值替換掉舊值
else
return t.setValue(value);
} while (t != null);
}
// 使用 key 數據類型中默認的比較器,後面邏輯相同
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);
}
// 上面的代碼如果直接return了表示。TreeMap中原來存在 key=參數key的節點,直接覆蓋返回
// 下面的代碼表示紅黑樹中不存在Key,需要爲其創建節點, parent上面循環得到的二叉樹的葉子及誒到哪
java.util.TreeMap.Entry<K,V> e = new java.util.TreeMap.Entry<>(key, value, parent);
// cmp < 0 表示當前key生成的Entry是parent的左節點
if (cmp < 0)
parent.left = e;
// cmp > 0 表示當前key生成的Entry是parent的右節點
else
parent.right = e;
// 插入新的節點後,紅黑樹進行修復【節點顏色變化,左右旋等】,保證其扔就滿足紅黑樹的特性
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
**
* 插入節點後,紅黑樹進行自我修復,保證其仍然滿足紅黑樹的要求
* 由於技術有限,暫時沒有進行解讀
* @param x
*/
private void fixAfterInsertion(java.util.TreeMap.Entry<K,V> x) {
x.color = RED;
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
java.util.TreeMap.Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
java.util.TreeMap.Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
如果大家瞭解二叉樹的話【要是懂得紅黑樹,就更完美了】,上面 put 方法也很容易理解了,每一行代碼的意思在已經標明註釋了,這裏不多說了。關於方法最後 fixAfterInsertion 調整紅黑樹結構的方法,由於本人對紅黑樹理解不夠透徹,也就沒有解讀。
get 方法
不論是 get 還是 put 方法,都是通過根節點 和 Comparable 對紅黑樹進行查找,最後實現功能,代碼如下:
// 通過 key 獲取到Value的值
public V get(Object key) {
java.util.TreeMap.Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
// 跟 key 值找到對應的 Entry
final java.util.TreeMap.Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
// TreeMap 中key 不可以爲空
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
// 得到TreeMap 中紅黑樹的 根節點
java.util.TreeMap.Entry<K,V> p = root;
// 循環遍歷
while (p != null) {
// key和當前節點的key比較
int cmp = k.compareTo(p.key);
// 根據二叉樹的原理,
// cmp < 0 找左節點
if (cmp < 0)
p = p.left;
// cmp > 0 找右節點
else if (cmp > 0)
p = p.right;
// 當前節點就是要查找的節點
else
return p;
}
return null;
}
TreeMap 中的 comparator 介紹:
- 我們通過上面的 put 和 get 方法,我們發現 comparator 在其中起到了查詢導向的作用,那麼 comparator 究竟是怎麼實現的,我們先來看一下 Comparator 接口:
public interface Comparator<T> {
/**
* 比較對象
* @param o1
* @param o2
* @return
*/
int compare(T o1, T o2);
}
我們在實際工作中可以編寫一個實現類,實現Comparator 並完成 compare 接口,例如我們實現一個Integer 倒序的 TreeMap:
public class Test {
public static void main(String[] args) throws ParseException {
Map treeMap = new TreeMap<String,String>(new IntegerComparator());
treeMap.put(1,"11");
treeMap.put(2,"22");
treeMap.put(3,"33");
treeMap.put(4,"44");
Iterator iter = treeMap.entrySet().iterator();
while(iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
System.out.println("key = "+entry.getKey()+ ";value = " + entry.getValue());
}
}
}
class IntegerComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
return -((Integer) o1 - (Integer)o2);
}
}
打印結果:
key = 4;value = 44
key = 3;value = 33
key = 2;value = 22
key = 1;value = 11
TreeMap的遍歷方式:
- 第一種:根據entrySet()獲取TreeMap的“鍵值對”的Set集合 【推薦】
- 第二種: 根據keySet()獲取TreeMap的“鍵”的Set集合, 然後再根據key取得Value值 【不推薦】
- 第三種: 根據value()獲取TreeMap的“值”的集合。
代碼示例如下:
public class Test {
public static void main(String[] args) throws ParseException {
Map treeMap = new TreeMap<Integer,String>(new IntegerComparator());
treeMap.put(1,"11");
treeMap.put(2,"22");
treeMap.put(3,"33");
treeMap.put(4,"44");
System.out.println(" 第一種:根據entrySet()獲取TreeMap的“鍵值對”的Set集合 【推薦】 ");
Iterator iter1 = treeMap.entrySet().iterator();
while(iter1.hasNext()) {
Map.Entry entry = (Map.Entry)iter1.next();
System.out.println("key = "+entry.getKey()+ ";value = " + entry.getValue());
}
System.out.println(" 第二種: 根據keySet()獲取TreeMap的“鍵”的Set集合, 然後再根據key取得Value值 【不推薦,因爲會遍歷兩次Map】 ");
Integer key = null;
Iterator iter2 = treeMap.keySet().iterator();
while(iter2.hasNext()) {
key = (Integer) iter2.next();
System.out.println("key = "+key+ ";value = " + treeMap.get(key));
}
System.out.println(" 第三種: 根據value()獲取TreeMap的“值”的集合。 ");
String value = null;
Iterator iter3 = treeMap.values().iterator();
while(iter3.hasNext()) {
value = (String) iter3.next();
System.out.println( "value = " + value);
}
}
}
class IntegerComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
return -((Integer) o1 - (Integer)o2);
}
}
打印結果:
第一種:根據entrySet()獲取TreeMap的“鍵值對”的Set集合 【推薦】
key = 4;value = 44
key = 3;value = 33
key = 2;value = 22
key = 1;value = 11
第二種: 根據keySet()獲取TreeMap的“鍵”的Set集合, 然後再根據key取得Value值 【不推薦,因爲會遍歷兩次Map】
key = 4;value = 44
key = 3;value = 33
key = 2;value = 22
key = 1;value = 11
第三種: 根據value()獲取TreeMap的“值”的集合。
value = 44
value = 33
value = 22
value = 11