基本概念
TreeMap集合是基于红黑树(Red-Black tree)的 NavigableMap实现。该集合最重要的特点就是可排序,该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。这句话是什么意思呢?就是说TreeMap可以对添加进来的元素进行排序,可以按照默认的排序方式,也可以自己指定排序方式。
根据上一条,我们要想使用TreeMap存储并排序我们自定义的类(如User类),那么必须自己定义比较机制:一种方式是User类去实现java.lang.Comparable接口,并实现其compareTo()方法。另一种方式是写一个类(如MyCompatator)去实现java.util.Comparator接口,并实现compare()方法,然后将MyCompatator类实例对象作为TreeMap的构造方法参数进行传参(当然也可以使用匿名内部类)。
对于红黑树的定义:
- 节点是红色或黑色。
- 根是黑色。
- 所有叶子都是黑色(叶子是NIL节点)。
- 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
- 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
成员变量和构造方法
private final Comparator<? super K> comparator;//比较器对象
private transient Entry<K,V> root;//根节点
private transient int size = 0;//集合大小
private transient int modCount = 0;//树结构被修改的次数
// 返回map的Entry视图
private transient EntrySet entrySet;
private transient KeySet<K> navigableKeySet;
private transient NavigableMap<K,V> descendingMap;
//静态内部类表示节点的类型
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;
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
}
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 cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
}
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;
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); // 调用比较器对象的compare()方法,该方法返回一个整数
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; // 取出比较器对象,即比较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); // 根据key找到父节点后新建一个节点
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
put的逻辑还是比较简单的,关键在于fixAfterInsertion对插入结点后的红黑树进行修复,维护其平衡
private void fixAfterInsertion(Entry<K,V> x) {
//约定插入的结点都是红节点
x.color = RED;
//x本身红色,如果其父节点也是红色,违反规则4,进行循环处理
while (x != null && x != root && x.parent.color == RED) {
//父节点是左结点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//获取父节点的右兄弟y
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
//p为左结点,y为红色 ①
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
// p为左结点,y为黑,x为右节点 ②
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
// p为左结点,y为红,x为左结点 ③
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
//父节点是右结点
} else {
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
//p为右结点,y为红色 ④
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
//p为右结点,y为黑色,x为左结点 ⑤
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
//p为右结点,y为黑色,x为右结点 ⑥
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
//约定根结点都是黑节点
root.color = BLACK;
}
总结一下有这么几种情况:
- 由于p为红,则x没有兄弟结点(原则4),要想插入后维持红黑树平衡,必须从p,pp以及p的兄弟结点y中找到对应的处理情况来平衡;
- 首先,插入的结点x,颜色肯定是红色,插入位置两种情况——左结点或者右结点;
- 其次,父结点p肯定为红色,因为父节点p为黑色的话,那就不用调整了(插入红节点对原则五并没有印象),所以只有父结点p是左结点和右结点的两种情况;
- 既然父结点p为红色,那么父父结点pp肯定为黑色(根据原则四),那么p的兄弟节点y,存在两种情况,颜色为红或者黑。
- 详情请参考:https://www.cnblogs.com/shianliang/p/9233117.html
get方法
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
getEntry(),一般以getEntry()方法为基础的获取元素的方法,其中包括containsKey(),get(),remove()等。
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)// 如果有比较器,则调用通过比较器的来比较key的方法
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;// 获取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;
}
参考:
https://blog.csdn.net/qq_32166627/article/details/72773293
https://www.cnblogs.com/warehouse/p/9346757.html