一、---使用方式---
TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
package ThreeWeek;
import java.util.*;
import java.util.Map.Entry;
public class TreeMapTest {
public static void main(String[] args) {
Food f1 = new Food("meet", 10);
Food f2 = new Food("egg", 15);
Food f3 = new Food("fruit", 5);
Food f4 = new Food("water", 20);
Map<Food, Integer> treeMap = new TreeMap<Food, Integer>();
treeMap.put(f1, 10);
treeMap.put(f2, 15);
treeMap.put(f3, 5);
treeMap.put(f4, 20);
// 将所有的事物都输出
Iterator<Entry<Food, Integer>> iter = treeMap.entrySet().iterator();
while(iter.hasNext()) {
Map.Entry<Food, Integer> entry = (Map.Entry<Food, Integer>)iter.next();
System.out.println(entry.getKey() + " - " + entry.getValue());
}
System.out.println(treeMap.hashCode());
}
}
// 实现内部比较器的接口
class Food implements Comparable<Food>{
String name;
int price;
Food(String name, int price) {
this.name = name;
this.price = price;
}
public String toString(){
return name+" 的价格是:";
}
public int compareTo(Food food){
if(food.price < this.price){
return -1;
}
if(food.price > this.price){
return 1;
}
if(food.price == this.price){
return this.name.compareTo(food.name);
}
return 0;
}
}
--------------------output--------------------
water 的价格是: - 20
egg 的价格是: - 15
meet 的价格是: - 10
fruit 的价格是: - 5
55112011
二、---内部原理---
1、继承关系
java.lang.Object
↳ java.util.AbstractMap<K, V>
↳ java.util.TreeMap<K, V>
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable {}
2、构造函数
(1)TreeMap中提供了四个构造函数,如下:
// 默认构造函数。使用该构造函数,TreeMap中的元素按照自然排序进行排列。
TreeMap()
// 创建的TreeMap包含Map
TreeMap(Map<? extends K, ? extends V> copyFrom)
// 指定Tree的比较器
TreeMap(Comparator<? super K> comparator)
// 创建的TreeSet包含copyFrom
TreeMap(SortedMap<K, ? extends V> copyFrom)
(2)如下是对各个构造方法的详解
// 默认构造函数
public TreeMap() {
comparator = null;
}
// 带比较器的构造函数
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
// 带Map的构造函数,Map会成为TreeMap的子集
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
//带SortedMap的构造函数,SortedMap会成为TreeMap的子集
public TreeMap(SortedMap<K, ? extends V> m) {
comparator = m.comparator();
try {
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
}
3、成员变量
(1)comparator是用来给比较器进行排序的
(2)root是红黑树的根节点
(3)size表示的是树中结点的数量
(4)modCount表示的是红黑树的修改次数
// 用于保持顺序的比较器,如果为空的话使用自然顺保持Key的顺序
private final Comparator<? super K> comparator;
// 根节点
private transient Entry<K,V> root = null;
// 树中的节点数量
private transient int size = 0;
// 多次在集合类中提到了,用于举了结构行的改变次数
private transient int modCount = 0;
Comparable接口支持泛型,只有一个方法,该方法返回负数、零、正数分别表示当前对象“小于”、“等于”、“大于”传入对象o
public interface Comparable<T> {
public int compareTo(T o);
}
compare(T o1,T o2)方法比较o1和o2两个对象,o1“大于”o2,返回正数,相等返回零,“小于”返回负数。
public interface Comparable<T> {
public int compareTo(T o);
}
三、---内部类Entry---
内部类Entry是比较重要的内容了,包括value,left,right,parent,color,构造方法颜色默认为黑色,获取key,获取value,修改并返回当前结点的value等。
static final class Entry<K,V> implements Map.Entry<K,V> {
// 键值对的“键”
K key;
// 键值对的“值”
V value;
// 左孩子
Entry<K,V> left = null;
// 右孩子
Entry<K,V> right = null;
// 父节点
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;
}
// 获取节点的key
public K getKey() {
return key;
}
// 获取节点的value
public V getValue() {
return value;
}
/**
* 修改并返回当前节点的value
*/
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
// 判断节点相等的方法(两个节点为同一类型且key值和value值都相等时两个节点相等)
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;
}
}
四、---红黑树---
红黑树顾名思义就是节点是红色或者黑色的平衡二叉树,它通过颜色的约束来维持着二叉树的平衡。
(1)每个节点都只能是红色或者黑色
(2)根节点是黑色
(3)每个叶节点(NIL节点,空节点)是黑色的。
(4)如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。
(5)从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这棵树大致上是平衡的。
2、增加结点
红黑树增加结点要满足上面的5条规则,当新增的结点不能满足上面的情况时,必须进行相应的调整,包括结点位置互换、颜色的互换和旋转等,旋转包括左旋和右旋。
(1)插入新节点总是红色节点 。
(2)如果插入节点的父节点是黑色, 能维持性质 。
(3)如果插入节点的父节点是红色, 破坏了性质. 故插入算法就是通过重新着色或旋转, 来维持性质 。
下面是TreeMap的put( )方法,通过它来具体分析一下怎么样进行增加结点的:
public V put(K key, V value) {
//用t表示二叉树的当前节点
Entry<K,V> t = root;
//t为null表示一个空树,即TreeMap中没有任何元素,直接插入
if (t == null) {
//比较key值,个人觉得这句代码没有任何意义,空树还需要比较、排序?
compare(key, key); // type (and possibly null) check
//将新的key-value键值对创建为一个Entry节点,并将该节点赋予给root
root = new Entry<>(key, value, null);
//容器的size = 1,表示TreeMap集合中存在一个元素
size = 1;
//修改次数 + 1
modCount++;
return null;
}
int cmp; //cmp表示key排序的返回结果
Entry<K,V> parent; //父节点
// split comparator and comparable paths
Comparator<? super K> cpr = comparator; //指定的排序算法
//如果cpr不为空,则采用既定的排序算法进行创建TreeMap集合
if (cpr != null) {
do {
parent = t; //parent指向上次循环后的t
//比较新增节点的key和当前节点key的大小
cmp = cpr.compare(key, t.key);
//cmp返回值小于0,表示新增节点的key小于当前节点的key,则以当前节点的左子节点作为新的当前节点
if (cmp < 0)
t = t.left;
//cmp返回值大于0,表示新增节点的key大于当前节点的key,则以当前节点的右子节点作为新的当前节点
else if (cmp > 0)
t = t.right;
//cmp返回值等于0,表示两个key值相等,则新值覆盖旧值,并返回新值
else
return t.setValue(value);
} while (t != null);
}
//如果cpr为空,则采用默认的排序算法进行创建TreeMap集合
else {
if (key == null) //key值为空抛出异常
throw new NullPointerException();
/* 下面处理过程和上面一样 */
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);
}
//将新增节点当做parent的子节点
Entry<K,V> e = new Entry<>(key, value, parent);
//如果新增节点的key小于parent的key,则当做左子节点
if (cmp < 0)
parent.left = e;
//如果新增节点的key大于parent的key,则当做右子节点
else
parent.right = e;
/*
* 上面已经完成了排序二叉树的的构建,将新增节点插入该树中的合适位置
* 下面fixAfterInsertion()方法就是对这棵树进行调整、平衡,具体过程参考上面的五种情况
*/
fixAfterInsertion(e);
//TreeMap元素数量 + 1
size++;
//TreeMap容器修改次数 + 1
modCount++;
return null;
}
上述的操作仅仅是插入代码,要想使红黑树平衡还需要进行平衡操作fixAfterInsertion(e);这个操作中包括左旋和右旋。
(1)没有儿子,即为叶结点。直接把父结点的对应儿子指针设为NULL,删除儿子结点就OK了。
(2)只有一个儿子。那么把父结点的相应儿子指针指向儿子的独生子,删除儿子结点也OK了。
(3)有两个儿子。这种情况比较复杂,但还是比较简单。上面提到过用子节点C替代代替待删除节点D,然后删除子节点C即可。
找到一个替代子节点C来替代P,然后直接删除C,最后调整这棵红黑树。下面代码是寻找替代节点、删除替代节点。
private void deleteEntry(Entry<K,V> p) {
modCount++; //修改次数 +1
size--; //元素个数 -1
/*
* 被删除节点的左子树和右子树都不为空,那么就用 p节点的中序后继节点代替 p 节点
* successor(P)方法为寻找P的替代节点。规则是右分支最左边,或者 左分支最右边的节点
* ---------------------(1)
*/
if (p.left != null && p.right != null) {
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
}
//replacement为替代节点,如果P的左子树存在那么就用左子树替代,否则用右子树替代
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
/*
* 删除节点,分为上面提到的三种情况
* -----------------------(2)
*/
//如果替代节点不为空
if (replacement != null) {
replacement.parent = p.parent;
/*
*replacement来替代P节点
*/
//若P没有父节点,则跟节点直接变成replacement
if (p.parent == null)
root = replacement;
//如果P为左节点,则用replacement来替代为左节点
else if (p == p.parent.left)
p.parent.left = replacement;
//如果P为右节点,则用replacement来替代为右节点
else
p.parent.right = replacement;
//同时将P节点从这棵树中剔除掉
p.left = p.right = p.parent = null;
/*
* 若P为红色直接删除,红黑树保持平衡
* 但是若P为黑色,则需要调整红黑树使其保持平衡
*/
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) { //p没有父节点,表示为P根节点,直接删除即可
root = null;
} else { //P节点不存在子节点,直接删除即可
if (p.color == BLACK) //如果P节点的颜色为黑色,对红黑树进行调整
fixAfterDeletion(p);
//删除P节点
if (p.parent != null) {
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}