TreeMap源码

TreeMap概叙:

  1. TreeMap存储K-V键值对,通过红黑树(R-B tree)实现;
  2. TreeMap继承了NavigableMap接口,NavigableMap接口继承了SortedMap接口,可支持一系列的导航定位以及导航操作的方法,当然只是提供了接口,需要TreeMap自己去实现;
  3. TreeMap实现了Cloneable接口,可被克隆,实现了Serializable接口,可序列化;
  4. 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     }

 

 

posted @ 2020-10-23 17:15  小鑫学JAVA  阅读(106)  评论(0)    收藏  举报