zoukankan      html  css  js  c++  java
  • Java集合——TreeMap源码详解

    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.  红黑树

    红黑树是一种二叉搜索树,让我们在一起回忆下二叉搜索树的一些性质:

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


    叶子节点为上图中的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>
    

    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){}
    }
    


    4.  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迭代器遍历
    Integer integ = null;
    Iterator iter = map.entrySet().iterator();
    while(iter.hasNext()) {
        Map.Entry entry = (Map.Entry)iter.next();
        key = (String)entry.getKey();
        integ = (Integer)entry.getValue();
    }
    
    //2.根据keySet()和Iterator迭代器遍历
    String key = null;
    Integer integ = null;
    Iterator iter = map.keySet().iterator();
    while (iter.hasNext()) {
        key = (String)iter.next();
        integ = (Integer)map.get(key);
    }
    

    9.  TreeMapHashMap的关系

    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

    同时请多点赞多多支持~

  • 相关阅读:
    linux系统中如何查看日志 (转)
    php 获取随机字符串(原创)
    php Aes 128位算法
    linux 在线实验
    number随时间随机递增每天 不同 php(原创)
    php 判断字符串包含中文(转)
    同步,异步 阻塞,非阻塞, 异步+回调机制 线程队列 事件Event 丶协程
    线程的理论知识 开启线程的两种方式(Thread) 线程和进程之间的对比 线程的其他方法 守护进程 互斥锁 死锁现象,递归锁 信号量
    获取进程以及父进程的pid 验证进程之间的数据隔离 join方法 进程对象的其他属性 僵尸进程与孤儿进程(存在Linux系统中) 守护进程
    进程基础知识 操作系统 操作系统的发展史(多道技术) 进程介绍 python并发编程之:多进程
  • 原文地址:https://www.cnblogs.com/qitian1/p/6461465.html
Copyright © 2011-2022 走看看