Map的单元是对键值对的处理,之前分析过的两种Map,HashMap和LinkedHashMap都是用哈希值去寻找我们想要的键值对,优点是理想情况下O(1)的查找速度。

  那如果我们在一个对查找性能要求不那么高,反而对有序性要求比较高的应用场景呢?

这个时候HashMap就不再适用了,我们需要一种新的Map,在JDK中提供了一个接口:SortedMap,我想分析一下具体的实现中的一种:TreeMap.

HashMap是Key无序的,而TreeMap是Key有序的。

  1. TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
  2. TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
  3. TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
  4. TreeMap 实现了Cloneable接口,意味着它能被克隆
  5. TreeMap 实现了java.io.Serializable接口,意味着它支持序列化
  6. TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
  7. TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。
  8. TreeMap是非同步的(线程不安全的)。 它的iterator 方法返回的迭代器是fail-fastl的。

源码分析:

1.看一下基本成员:

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 Entry<K,V> root = null;
    private transient int size = 0;
    private transient int modCount = 0;
    public TreeMap() {
        comparator = null;
    }    
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
    //后面省略
}

  TreeMap继承了NavigableMap,而NavigableMap继承自SortedMap,为SortedMap添加了搜索选项,NavigableMap有几种方法,分别是不同的比较要求:floorKey是小于等于,ceilingKey是大于等于,lowerKey是小于,higherKey是大于。

  注意初始化的时候,有一个Comparator成员,这是用于维持有序的比较器,当我们想做一个自定义数据结构的TreeMap时,可以重写这个比较器。

2.我们看一下Entry的成员:

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;
    //后续省略
}

  咦?木有了熟悉了哈希值,多了left,right,parent,这是我们的树结构,最后看到color,明白了:TreeMap是基于红黑树实现的!而且默认的节点颜色是黑色。

  至于红黑树,想必多多少少都听过,这是一种平衡的二叉查找树,是2-3树的一种变体,即拥有二叉查找树的高效查找,拥有2-3树的高效平衡插入能力。

  红黑树巧妙的增加了颜色这个维度对2-3树的树本身进行了降维成了二叉树,这样树的调整不会再如2-3树那么繁琐。

  有的同学看到这里会质疑我,你这个胡说八道,和算法导论里讲的不一样!

  对,CLRS中确实没有这段,这段选自《Algorithms》,我觉得提供了一种有趣的理解思路,所以如果之前只看了CLRS,建议去看一下这本书,互相验证。

  不过为了尊重JDK的作者,后面的还是按照CLRS中的讲解来吧,毕竟在JDK源码的注释中写着:From CLR。

  我们在红黑树中的一切插入和删除后,为了维护树的有序性的动作看起来繁复,但都是为了维护下面几个红黑树的基本性质

  1. 树的节点只有红与黑两种颜色
  2. 根节点为黑色的
  3. 叶子节点为黑色的
  4. 红色节点的字节点必定是黑色的
  5. 从任意一节点出发,到其后继的叶子节点的路径中,黑色节点的数目相同

  红黑树的第4条性质保证了这些路径中的任意一条都不存在连续的红节点,而红黑树的第5条性质又保证了所有的这些路径上的黑色节点的数目相同。因而最短路径必定是只包含黑色节点的路径,而最长路径为红黑节点互相交叉的路径,由于所有的路径的起点必须是黑色的,而红色节点又不能连续存在,因而最长路径的长度为全为黑色节点路径长度的二倍

回到TreeMap本身,看看它的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);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    else {
        if (key == null)
            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);
    }
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}
View Code

相关文章:

  • 2021-12-18
  • 2021-09-12
  • 2022-12-23
  • 2021-09-30
  • 2022-12-23
  • 2021-12-05
  • 2021-10-17
  • 2021-05-09
猜你喜欢
  • 2021-05-05
  • 2021-10-11
  • 2021-12-22
  • 2021-06-05
  • 2021-11-14
相关资源
相似解决方案