Java集合——TreeMap源码详解

时间:2022-01-08 17:17:18

0. 前言

本文对TreeMap的源码进行了解析,本文原创,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52746000

先对TreeMap的特性进行一个概述: 

1TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。因为红黑树是平衡的二叉搜索树,所以其put(包含update操作)、getremove的时间复杂度都为log(n)

(2)TreeMap 相比于HashMap多实现了了NavigableMap接口(也就是这个接口,决定了TreeMapHashMap的不同:HashMapkey是无序的,TreeMapkey是有序的)。

public class TreeMap<K,V>       extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable

3TreeMap非同步的。


1.  红黑树

红黑树是一种 二叉搜索树 ,让我们在一起回忆下二叉搜索树的一些性质:
Java集合——TreeMap源码详解

二叉搜索树左子树的值小于根节点,右子树的值大于根节点。很明显,二叉搜索树每进行一次判断就是能将问题的规模减少一半,所以二叉搜索树查找元素的时间复杂度为log(n)。下面在看一下红黑树的样子:

Java集合——TreeMap源码详解

叶子节点为上图中的NIL节点,国内一些教材中没有这个NIL节点,我们在画图时有时也会省略这些NIL节点,但是我们需要明确,当我们说叶子节点时,指的就是这些NIL节点。

 红黑树通过下面5条规则,保证了树是平衡的:

1)树的节点只有红与黑两种颜色。

2)根节点为黑色的。

3)叶子节点为黑色的。

4)红色节点的子节点必定是黑色的。

5)从任意节点出发,到其后继的叶子节点的路径中,黑色节点的数目相同。

满足了上面5个条件后,就能够保证根节点到叶子节点的最长路径不会大于根节点到叶子最短路径的2

简单证明如下:假设根节点到叶子节点最短的路径中,黑色节点数目为B,那么根据性质5,根节点到叶子节点的最长路径中,黑色节点数目也是B,最长的情况就是每两个黑色节点中间有个红色节点(也就是红黑相间的情况),所以红色节点最多为B1个。所以B+B-1<=2B得证。

 

红黑树操作包括插入、删除、左旋、右旋,这里有个可视化的红黑树操作网站,把这些操作都按动画做出来了,生动形象。

 

2.  NavigableMap接口

public interface NavigableMap<K,V> extends SortedMap<K,V>public interface SortedMap<K,V> extends Map<K,V>
Java集合——TreeMap源码详解

TreeMap实现了NavigableMap接口,NavigableMapJDK1.6新增的,NavigableMap继承了SortedMap(这个Map是有序的),顺序一般是指由Comparable接口提供的keys自然序,或者也可以在创建SortedMap实例时,指定一个Comparator来决定。插入SortedMap中的key的类都必须继承Comparable类(或指定一个comparator,这样才能通过k1.compareTo(k2)comparator.compare(k1, k2)来比较两个key

 NavigableMap接口在SortedMap的基础上增加了一些导航方法来返回与搜索目标最近的元素。例如下面这些方法:

lowerEntry//返回所有比给定Map.Entry小的元素floorEntry//返回所有比给定Map.Entry小或相等的元素ceilingEntry//返回所有比给定Map.Entry大或相等的元素higherEntry//返回所有比给定Map.Entry大的元素


3.  TreeMap 的存储结构

Entry作为TreeMap存储的结点,包括键、值、父结点、左孩子、右孩子、颜色。源码如下所示:

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;   //构造函数   Entry(K key, V value, Entry<K,V> parent){      this.key = key;      this.value = value;      this.parent = parent;    }   ......}


4.  TreeMap的构造方法

TreeMap有四种构造函数,分别对应不同的参数。不同构造方法的功能已经在注释里标明了。

//1.键自然顺序public TreeMap() {        comparator = null;}//2.根据给定比较器进行排序public TreeMap(Comparator<? super K> comparator){        this.comparator = comparator;}//3.构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序进行排序public TreeMap(Map<? extends K, ? extends V> m) {        comparator = null;        putAll(m);}// putAll()将m中的所有元素添加到TreeMap中public void putAll(Map<? extends K, ? extends V> m) {    for (Map.Entry<? extends K, ? extends V> e : m.entrySet())        put(e.getKey(), e.getValue());}//4.构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射public TreeMap(SortedMap<K, ? extends V> m){    comparator = m.comparator();    try{      buildFromSorted(m.size(), m.entrySet().iterator(), null, null);}    catch (Exception e){}}


5.  TreeMapput操作

public V put(K key, V value) {    Entry<K,V> t = root;    //若根节点为空,则以(key,value)为参数新建节点    if (t == null){      compare(key, key); // type check      root = new Entry<>(key, value, null);//第三个参数为父节点      size = 1;      modCount++;      return null;}    int cmp;Entry<K,V> parent;    Comparator<? super K> cpr = comparator; //自定义比较器    if (cpr != null) {//优先通过比较器比较两个结点的大小      do {parent = t;          cmp = cpr.compare(key, t.key);          if (cmp < 0)  //表示新增节点的key小于当前及节点的key,准备工作           t = t.left;          else if (cmp > 0) //表示新增节点的key大于当前及节点的key,准备工作          t = t.right;          else              return t.setValue(value);  //相等则覆盖旧值         } while (t != null); //直到把新节点要插入的位置置空    }    //如果没有定义比较器,则采用默认的排序算法进行创建TreeMap集合    else{        if (key == null)           throw new NullPointerException();        @SuppressWarnings("unchecked")        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);     if (cmp < 0)        parent.left = e;     else        parent.right = e;     //进行着色和旋转等操作修复红黑树      fixAfterInsertion(e);     size++;     modCount++;     return null;}

从源码中可以总结出几个需要注意的点:

1TreeMap的查询和更新都涉及比较操作,故TreeMap键必须实现Comparable接口或者构造时传入比较器(比较器优先级更高)。

2默认的排序算法中put操作不允许null,但是值(value)允许为null;若传入自定义比较器,可以手动处理null键的情况。

3)键重复的情况下,新值会覆盖掉旧值


6.  TreeMapget操作

public V get(Object key) {//查询操作         Entry<K,V> p = getEntry(key);         return (p==null ? null : p.value);  }  final Entry<K,V> getEntry(Object key) {//跟普通二叉排序树的查询操作一致          if (comparator != null)//存在比较器              return getEntryUsingComparator(key);//根据比较器定义的比较规则查找          if (key == null)              throw new NullPointerException();          Comparable<? super K> k = (Comparable<? super K>) key;          Entry<K,V> p = root;          while (p != null) {//根据Comparable接口定义的比较规则查找              int cmp = k.compareTo(p.key);              if (cmp < 0)//待查结点在左子树                  p = p.left;              else if (cmp > 0)//待查结点在右子树                  p = p.right;              else                  return p;          }          return null;//没找到      }   final Entry<K,V> getEntryUsingComparator(Object key) {//根据比较器定义的比较规则查找          K k = (K) key;          Comparator<? super K> cpr = comparator;          if (cpr != null) {              Entry<K,V> p = root;              while (p != null) {                  int cmp = cpr.compare(k, p.key);                  if (cmp < 0)                      p = p.left;                  else if (cmp > 0)                      p = p.right;                  else                      return p;              }          }          return null;  } 


7.  TreeMapremove操作

public V remove(Object key) {        Entry<K,V> p = getEntry(key);//首先找到待删结点        if (p == null)            return null;        V oldValue = p.value;        deleteEntry(p);//删除结点,并修复红黑树      return oldValue;  }  


8.  TreeMap的两种迭代操作

//1.根据entrySet()和Iterator迭代器遍历
TreeMap treeMap = new TreeMap<>();
treeMap.put(1, "1");
treeMap.put(3, "3");
treeMap.put(4, "4");
treeMap.put(2, "2");
treeMap.put(3, "33");
//遍历
Integer key = null;
String value = null;
Iterator iter = treeMap.entrySet().iterator();
while(iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
key = (Integer)entry.getKey();
value = (String)entry.getValue();
System.out.println("the key is "+ key+", and the value is "+value);
}
//2.根据keySet()和Iterator迭代器遍历
TreeMap treeMap = new TreeMap<>();
treeMap.put(1, "1");
treeMap.put(3, "3");
treeMap.put(4, "4");
treeMap.put(2, "2");
treeMap.put(3, "33");
//遍历
String value = null;
Integer key = null;
Iterator iter = treeMap.keySet().iterator();
while (iter.hasNext()) {
key = (Integer)iter.next();
value = (String)treeMap.get(key);
System.out.println("the key is "+ key+", and the value is "+value);
}

输出结果都为:

the key is 1, and the value is 1the key is 2, and the value is 2the key is 3, and the value is 33the key is 4, and the value is 4


9.  TreeMap HashMap 的关系

9.1    TreeMapHashMap的相同点

1)两者均是线程不安全的。

2)两者插入节点时,key重复均覆盖旧值。

 

9.2  TreeMapHashMap的不同点

1HashMap的实现基于数组和单链表,而TreeMap的实现基于红黑树,必要时数组会扩容、保持树平衡。

2HashMapkey无序的,TreeMapkey有序的。

3TreeMap要求key必须实现Comparable接口,或者初始化时传入Comparator比较器。

4HashMap增删改查操作的时间复杂度为O(1)TreeMap增删改查操作的时间复杂度为O(log(n))

5HashMap允许null值,TreeMap默认的排序算法中put操作不允许null,但是值(value)允许为null;若传入自定义比较器,可以手动处理null键的情况。


10.  TreeMapTreeSet的关系

TreeMap Map 接口的常用实现类,而TreeSet Set 接口的常用实现类。虽然 TreeMap TreeSet 实现的接口规范不同,但 TreeSet 底层是通过 TreeMap 来实现的(如同HashSet底层是是通过HashMap来实现、LinkedHashSet底层基于LinkedHashMap实现一样),因此二者的实现方式完全一样,都是红黑树算法。

 

相同点:

1TreeMapTreeSet都是有序的集合(仅仅key对象有序)。

2TreeMapTreeSet都是非同步集合,不过都可以使用Collections.synchroinzedMap()来实现同步。

3)内部对元素的操作时间复杂度都是O(logN),而HashMap/HashSet/HashTable则为O(1)

 

不同点:

最主要的区别就是TreeSetTreeMap分别实现SetMap接口,相同的道理可以用于HashSetHashMapLinkedHashMapLinkedHashSet

TreeSet只存储一个对象(通过MapKey的部分实现,下面贴出的HashSet的构造方法源码可以证明),而TreeMap存储两个对象KeyValue,前者通过put()将元素放入Map,后者使用add()将元素放入SetHashSet内部是大量借用HashMap的实现,它本身不过是调用HashMap的一个代理。

private transient HashMap<E,Object> map;    // Dummy value to associate with an Object in the backing Map    private static final Object PRESENT = new Object();    /**     * Constructs a new, empty set; the backing HashMap instance has     * default initial capacity (16) and load factor (0.75).     */    public HashSet() {    map = new HashMap<E,Object>();    }public boolean add(E e) {          return map.put(e, PRESENT)==null;      }    public boolean remove(Object o) {          return map.remove(o)==PRESENT;      }    public boolean contains(Object o) {          return map.containsKey(o);      }  

转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52746000

同时请多点赞多多支持~