TreeMap源码
TreeMap概叙:
- TreeMap存储K-V键值对,通过红黑树(R-B tree)实现;
- TreeMap继承了NavigableMap接口,NavigableMap接口继承了SortedMap接口,可支持一系列的导航定位以及导航操作的方法,当然只是提供了接口,需要TreeMap自己去实现;
- TreeMap实现了Cloneable接口,可被克隆,实现了Serializable接口,可序列化;
- TreeMap因为是通过红黑树实现,红黑树结构天然支持排序,默认情况下通过Key值的自然顺序进行排序;
源码分析:
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
从这可以看出TreeMap继承了AbstractMap,实现了NavigableMap<K,V>, Cloneable, java.io.Serializable,其中Cloneable,Serializable表示TreeMap可以使用克隆和序列化,
NavigableMap的父接口SortedMap
提供了获取最大值与最小值的方法,但对于一个已经排序的数据集,除了最大值与最小值之外,我们还想对任何一个元素,找到比它小的值和比它大的值,还可以按照原有的顺序倒序排序等。
NavigableMap接口提供的如下方法
// 找到第一个比指定的key小的值 Map.Entry<K,V> lowerEntry(K key); // 找到第一个比指定的key小的key K lowerKey(K key); // 找到第一个小于或等于指定key的值 Map.Entry<K,V> floorEntry(K key); // 找到第一个小于或等于指定key的key K floorKey(K key); // 找到第一个大于或等于指定key的值 Map.Entry<K,V> ceilingEntry(K key); //找到第一个大于或等于指定key的key K ceilingKey(K key); // 找到第一个大于指定key的值 Map.Entry<K,V> higherEntry(K key); //找到第一个大于指定key的key K higherKey(K key); // 获取最小值 Map.Entry<K,V> firstEntry(); // 获取最大值 Map.Entry<K,V> lastEntry(); // 删除最小的元素 Map.Entry<K,V> pollFirstEntry(); // 删除最大的元素 Map.Entry<K,V> pollLastEntry(); //返回一个倒序的Map NavigableMap<K,V> descendingMap(); // 返回一个Navigable的key的集合,NavigableSet和NavigableMap类似 NavigableSet<K> navigableKeySet(); // 对上述集合倒序 NavigableSet<K> descendingKeySet();
TreeMap的主要成员
/** *用于在TreeMap维护顺序的比较器,如果使用其键的自然顺序,则为null。 *在java泛型中,? 表示通配符,代表未知类型,< ? extends Object>表示上边界限定通配符,< ? super Object>表示下边界限定通配符。 * @serial */ private final Comparator<? super K> comparator;
//TreeMap的存储结构既然是红黑树,那么必然会有唯一的根节点。
private transient Entry<K,V> root;
其核心方法为put方法,其源码如下
1 public V put(K key, V value) {
2 Entry<K,V> t = root;
3 if (t == null) { //判断其根绝点是否为null,如果为空则表示这是一个空map
4 compare(key, key); // 类型检查
5
6 root = new Entry<>(key, value, null);//创建一个根节点
7 size = 1;//将该map的size设置为1
8 modCount++;//修改次数+1
9 return null;
10 }
11 int cmp;
12 Entry<K,V> parent;
13 // 分裂比较器和可比路径
14 Comparator<? super K> cpr = comparator;
15 if (cpr != null) {//如果比较器为null。默认使用顺序排序
16 do {
17 parent = t;//根节点
18 cmp = cpr.compare(key, t.key);//比较插入的key和跟节点的key
19 if (cmp < 0)//如果插入的key小于根节点的key,则放在跟节点的左边节点,否则放在根节点的右边节点
20 t = t.left;//将根结点的左边的子节点拿出来给t
21 else if (cmp > 0)
22 t = t.right;//同理
23 else
24 return t.setValue(value);//如果一样,则直接将该值赋值给根节点
25 } while (t != null);//知道t为null结束
26 }
27 else {//使用自定义的排序规则排序
28 if (key == null)//key不能为null
29 throw new NullPointerException();
30 @SuppressWarnings("unchecked")
31 Comparable<? super K> k = (Comparable<? super K>) key;//获取自定义的比较器
32 do {
33 parent = t;//初次进来,parent为根节点,循环后为父节点
34 cmp = k.compareTo(t.key);//compareTo方法为我们自定义的规则
35 if (cmp < 0)//同上
36 t = t.left;
37 else if (cmp > 0)
38 t = t.right;//同上
39 else
40 return t.setValue(value);//同上
41 } while (t != null);
42 }
43 Entry<K,V> e = new Entry<>(key, value, parent);//创建一个节点,该节点是需要插入的值
44 if (cmp < 0)//自定义排序小于0,则放左边节点,否则放右边节点
45 parent.left = e;
46 else
47 parent.right = e;
/**
* 节点加进去了,并不算完,一般情况下加入节点都会对红黑树的结构造成
* 破坏,我们需要通过一些操作来进行自动平衡处置,如【变色】【左旋】【右旋】
*/
48 fixAfterInsertion(e);
49 size++;
50 modCount++;
51 return null;
52 }
无需调整 | 【变色】即可实现平衡 | 【旋转+变色】才可实现平衡 | |
---|---|---|---|
情况1: | 当父节点为黑色时插入子节点 | 空树插入根节点,将根节点红色变为黑色 | 父节点为红色左节点,叔父节点为黑色,插入左子节点,那么通过【左左节点旋转】 |
情况2: | - | 父节点和叔父节点都为红色 | 父节点为红色左节点,叔父节点为黑色,插入右子节点,那么通过【左右节点旋转】 |
情况3: | - | - | 父节点为红色右节点,叔父节点为黑色,插入左子节点,那么通过【右左节点旋转】 |
情况4: | - | - | 父节点为红色右节点,叔父节点为黑色,插入右子节点,那么通过【右右节点旋转】 |
那我们再来看下fixAfterInsertion(e)方法
private void fixAfterInsertion(Entry<K,V> x) { x.color = RED;//首先将插入的节点的颜色赋值为红色 while (x != null && x != root && x.parent.color == RED) {//如果该节点的父节点颜色也是红色。原因:红黑树父节点为黑色时,并不需要进行树结构调整,
//只有当父节点为红色时,才需要调整 if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//父节点为叔父的左边子节点 Entry<K,V> y = rightOf(parentOf(parentOf(x)));//y为叔父节点的右边子节点 if (colorOf(y) == RED) {//如果右边节点的颜色为红色 setColor(parentOf(x), BLACK);//将父节点的颜色变成黑色 setColor(y, BLACK);//将叔父节点的右子边节点变成黑色 setColor(parentOf(parentOf(x)), RED);将叔父节点变成红色 x = parentOf(parentOf(x));并将叔父节点赋值给x } else { if (x == rightOf(parentOf(x))) {//如果父节点为右边节点 x = parentOf(x);//将父节点赋值给x rotateLeft(x);//左旋 } setColor(parentOf(x), BLACK);//父节点设置为黑色 setColor(parentOf(parentOf(x)), RED);//叔父节点设置为红色 rotateRight(parentOf(parentOf(x)));//右旋 } } else { Entry<K,V> y = leftOf(parentOf(parentOf(x)));//y为叔父节点的左节点 if (colorOf(y) == RED) {//如果其颜色为红色 setColor(parentOf(x), BLACK);//将父节点设置为黑色 setColor(y, BLACK);//叔父节点为黑色 setColor(parentOf(parentOf(x)), RED);//叔父节点为红色 x = parentOf(parentOf(x));//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; }
源码中通过 rotateLeft 进行【左旋】,通过 rotateRight 进行【右旋】。都非常类似,我们就看一下【左旋】的代码,【左旋】规则如下:“逆时针旋转两个节点,让一个节点被其右子节点取代,而该节点成为右子节点的左子节点”。
private void rotateLeft(Entry<K,V> p) { if (p != null) { /** * 断开当前节点p与其右子节点的关联,重新将节点p的右子节点的地址指向节点p的右子节点的左子节点 * 这个时候节点r没有父节点 */ Entry<K,V> r = p.right; p.right = r.left; //将节点p作为节点r的父节点 if (r.left != null) r.left.parent = p; //将节点p的父节点和r的父节点指向同一处 r.parent = p.parent; //p的父节点为null,则将节点r设置为root if (p.parent == null) root = r; //如果节点p是左子节点,则将该左子节点替换为节点r else if (p.parent.left == p) p.parent.left = r; //如果节点p为右子节点,则将该右子节点替换为节点r else p.parent.right = r; //重新建立p与r的关系 r.left = p; p.parent = r; } }
就算是看了上面的注释还是并不清晰,看下图你就懂了
get方法:
public V get(Object key) { Entry<K,V> p = getEntry(key); return (p==null ? null : p.value); }
getEntry:
1 final Entry<K,V> getEntry(Object key) { 2 // Offload comparator-based version for sake of performance 3 if (comparator != null)//如果比较器为null,采用默认比较器 4 return getEntryUsingComparator(key); 5 if (key == null)//如果key为null报错抛出异常 6 throw new NullPointerException(); 7 @SuppressWarnings("unchecked") 8 Comparable<? super K> k = (Comparable<? super K>) key;//获取比较器 9 Entry<K,V> p = root; 10 while (p != null) { 11 int cmp = k.compareTo(p.key); 12 if (cmp < 0) 13 p = p.left; 14 else if (cmp > 0) 15 p = p.right; 16 else 17 return p; 18 } 19 return null; 20 }