Java集合系列之TreeMap源码分析

一、概述

  TreeMap是基于红黑树实现的。由于TreeMap实现了java.util.sortMap接口,集合中的映射关系是具有一定顺序的,该映射根据其键的自然顺序进行排序或者根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法。另外TreeMap中不允许键对象是null。

  1、什么是红黑树?

  红黑树是一种特殊的二叉排序树,主要有以下几条基本性质:

  • 每个节点都只能是红色或者黑色
  • 根节点是黑色
  • 每个叶子节点是黑色的
  • 如果一个节点是红色的,则它的两个子节点都是黑色的
  • 从任意一个节点到每个叶子节点的所有路径都包含相同数目的黑色节点

  红黑树的具体原理分析和算法设计可参见博文:红黑树的原理分析和算法设计

  2、key的两种排序方式

  自然排序:TreeMap的所有key必须实现Comparable接口,并且所有key应该是同一个类的对象,否则将会抛ClassCastException异常

  指定排序:这种排序需要在构造TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的key进行排序

  3、TreeMap类的继承关系

public class TreeMap<K,V> 

  extends AbstractMap<K,V>

    implements NavigableMap<K,V>, Cloneable, Serializable

  其中,NavigableMap接口是扩展的SortMap,具有了针对给定搜索目标返回最接近匹配项的导航方法。其方法 lowerEntryfloorEntryceilingEntry 和 higherEntry 分别返回与小于、小于等于、大于等于、大于给定键的键关联的 Map.Entry 对象,如果不存在这样的键,则返回 null。类似地,方法 lowerKeyfloorKeyceilingKey 和 higherKey 只返回关联的键。所有这些方法是为查找条目而不是遍历条目而设计的。

二、TreeMap源码分析

  1、存储结构

  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;
        }
        ......
}

  2、构造函数

  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);
    }
    //4.构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射
    public TreeMap(SortedMap<K, ? extends V> m)
    {
        comparator = m.comparator();
        try
        {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        }
        catch (java.io.IOException cannotHappen)
        {
        }
        catch (ClassNotFoundException cannotHappen)
        {
        }
    }

  3、TreeMap常用方法

  V put(K key,V value):将键值对(key,value)添加到TreeMap中

public V put(K key, V value)
    {
        Entry<K,V> t = root;
        //若根节点为空,则以(key,value)为参数新建节点
        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)  //表示新增节点的key小于当前及节点的key,则以当前节点的左子节点作为新的当前节点
                    t = t.left;
                else if (cmp > 0) //表示新增节点的key大于当前及节点的key,则以当前节点的右子节点作为新的当前节点
                    t = t.right;
                else
                    return t.setValue(value);  //相等则覆盖旧值
            } while (t != null);
        }
        //如果cpr为空,则采用默认的排序算法进行创建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调整红黑树
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

  Set<Map.Entry<K,V>> entrySet():返回此映射中包含的映射关系的Set视图

 public Set<Map.Entry<K,V>> entrySet()
     {
        EntrySet es = entrySet;
        return (es != null) ? es : (entrySet = new EntrySet());
    }

  boolean remove(Object o): 如果此 TreeMap 中存在该键的映射关系,则将其删除

 public boolean remove(Object o)
    {
                if (!(o instanceof Map.Entry))
                    return false;
                Map.Entry<K,V> entry = (Map.Entry<K,V>) o;
                K key = entry.getKey();
                if (!inRange(key))
                    return false;
                TreeMap.Entry<K,V> node = m.getEntry(key);
                if (node!=null && valEquals(node.getValue(),
                                            entry.getValue()))
               {
                    m.deleteEntry(node);
                    return true;
                }
                return false;
            }
    }       

三、TreeMap应用示例代码

public class TreeMapDemo
{
    public static void main(String[] args)
    {
        //使用键的自然顺序构造一个新的、空的树映射
        TreeMap<String,String> tm=new TreeMap<>();
        tm.put("001", "中国");
        tm.put("003", "美国");
        tm.put("002", "法国");
        System.out.println("调用entrySet得到键值对集:");
        Set<Entry<String, String>> result=tm.entrySet();
        for(Entry<String, String> result2:result)
        {
            System.out.println(result2.getKey()+"---"+result2.getValue());
        }
        System.out.println("调用keySet得到键集:");
        Set<String> result3=tm.keySet();
        for(String str:result3)
        {
            System.out.println(str);
        }
        System.out.println("调用values得到值集:");
        Collection result4=tm.values();
        for(Object str:result4)
            System.out.println(str);    

        //新建一个带比较器的TreeMap
        TreeMap<String,String> tm2=new TreeMap<>(new ComparatorDemo());
        tm2.put("001", "中国");
        tm2.put("003", "美国");
        tm2.put("002", "法国");
        Set<Entry<String, String>> result5=tm2.entrySet();
        for(Entry<String, String> result2:result5)
        {
            System.out.println(result2.getKey()+"---"+result2.getValue());
        }
    }
}

  首先按照键的自然顺序构建TreeMap,加入元素并遍历:

  

  然后新建一个比较器类,实现Comparator接口

public class ComparatorDemo implements Comparator<String>
{

    public int compare(String o1, String o2) {
        return 1;
    }

}

  在带比较器的tm2中,按照与tm1相同的顺序添加元素,此时再遍历tm2,结果如下:

  

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-02 11:09:36

Java集合系列之TreeMap源码分析的相关文章

Java集合系列之LinkedList源码分析

一.LinkedList简介 LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的. ps:这里有一个问题,就是关于实现LinkedList的数据结构是否为循环的双向链表,上网搜了有很多文章都说是循环的,并且有的文章中但是我看了源代码觉得应该不是循环的? 例如在删除列表尾部节点的代码: private E unlinkLast(Node<E> l) { final E element = l.item; final Node<E> pr

Java集合系列:-----------03ArrayList源码分析

上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayList.先对ArrayList有个整体认识,再学习它的源码,最后再通过例子来学习如何使用它.内容包括: ArrayList简介 ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAccess

Java集合系列之ArrayList源码分析

一.ArrayList简介 ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类. 该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加.如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能. ArrayList的用法和Vector向类似,但是Vect

Java集合系列之HashMap源码分析

一.HashMap简介 HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射.此类不保证映射的顺序,假定哈希函数将元素适当的分布在各桶之间,可为基本操作(get和put)提供稳定的性能. ps:本文中的源码来自jdk1.8.0_45/src. 1.重要参数 HashMap的实例有两个参数影响其性能. 初始容量:哈希表中桶的数量 加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度 当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的

Java集合系列之HashSet源码分析

一.HashSet简介 HashSet是Set接口典型实现,它按照Hash算法来存储集合中的元素,具有很好的存取和查找性能.主要具有以下特点: 不保证set的迭代顺序 HashSet不是同步的,如果多个线程同时访问一个HashSet,要通过代码来保证其同步 集合元素值可以是null 当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该值确定对象在HashSet中的存储位置.在Hash集合中,不能同时存放两个相等的

java集合框架10——TreeMap和源码分析(一)

前面讨论完了HashMap和HashTable的源码,这一节我们来讨论一下TreeMap.先从整体上把握TreeMap,然后分析其源码,深入剖析TreeMap的实现. 1. TreeMap简介 TreeMap是一个有序的key-value集合,它内部是通过红-黑树实现的,如果对红-黑树不太了解,请先参考下这篇博文:红-黑树.下面我们先来看看TreeMap的继承关系: java.lang.Object ? java.util.AbstractMap<K, V> ? java.util.TreeM

Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式

在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概念,主要讲了AQS的排队区是怎样实现的,什么是独占模式和共享模式以及如何理解结点的等待状态.理解并掌握这些内容是后续阅读AQS源码的关键,所以建议读者先看完我的上一篇文章再回过头来看这篇就比较容易理解.在本篇中会介绍在独占模式下结点是怎样进入同步队列排队的,以及离开同步队列之前会进行哪些操作.AQS为在独占模

Java并发系列[5]----ReentrantLock源码分析

在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可见性.在大多数情况下,这些机制都能很好地完成工作,但却无法实现一些更高级的功能,例如,无法中断一个正在等待获取锁的线程,无法实现限定时间的获取锁机制,无法实现非阻塞结构的加锁规则等.而这些更灵活的加锁机制通常都能够提供更好的活跃性或性能.因此,在Java5.0中增加了一种新的机制:Reentrant

java集合框架11——TreeMap和源码分析(二)

我们继续分析TreeMap的源码 1.TreeMap源码分析(续) 1. 存取方法 TreeMap中的存取方法本质上就是对红黑树的插入和删除操作,从源码里体现的更为明显,其实就是对红黑树的插入和删除(可以参考:红黑树),下面简单看下源码: /*************************** put和remove **********************************/ //将key-value对添加到TreeMap中,理解TreeMap的前提是理解红黑树 //因为和红黑树中的