java容器类---TreeMap、TreeSet
来源:互联网 发布:hits算法题目 编辑:程序博客网 时间:2024/05/01 23:40
1、TreeMap 简介
TreeMap是基于红黑树实现的,这里只对红黑树做个简单的介绍,红黑树是一种特殊的二叉排序树,关于二叉排序树,红黑树通过一些限制,使其不会出现二叉树排序树中极端的一边倒的情况,相对二叉排序树而言,这自然提高了查询的效率。
红黑树的基本性质如下:
1、每个节点都只能是红色或者黑色
2、根节点是黑色
3、每个叶节点(NULL节点,空节点)是黑色的。
4、如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。
5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
在介绍TreeMap前先介绍Comparable和Comparator接口。
Comparable接口:
public interface Comparable<T> { public int compareTo(T o); }Comparable接口支持泛型,只有一个方法,该方法返回负数、零、正数分别表示当前对象“小于”、“等于”、“大于”传入对象o。
Comparator接口:
public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }compare(T o1,T o2)方法比较o1和o2两个对象,o1“大于”o2,返回正数,相等返回零,“小于”返回负数。
equals(Object obj)返回true的唯一情况是obj也是一个比较器(Comparator)并且比较结果和此比较器的结果的大小次序是一致的。即comp1.equals(comp2)意味着sgn(comp1.compare(o1,o2))==sgn(comp2.compare(o1,o2))。
补充:符号sgn(expression)表示数学上的signum函数,该函数根据expression的值是负数、零或正数,分别返回-1、0或1。
1.1 数据结构
TreeMap的排序是基于对key的排序实现的,它的每一个Entry代表红黑树的一个节点,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; // 构造函数 Entry(K key, V value, Entry<K,V> parent) { this.key = key; this.value = value; this.parent = parent; } ........ }1.2 继承关系
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.SerializableNavigableMap接口扩展的SortedMap,具有了针对给定搜索目标返回最接近匹配项的导航方法。方法lowerEntry、floorEntry、ceilingEntry和higherEntry分别返回与小于、小于等于、大于等于、大于给定键的键关联的Map.Entry对象,如果不存在这样的键,则返回null。类似地,方法lowerKey、floorKey、ceilingKey和higherKey只返回关联的键。
1.3 成员变量
// 用于保持顺序的比较器,如果为空的话使用自然顺保持Key的顺序 private final Comparator<? super K> comparator; // 根节点 private transient Entry<K,V> root = null; // 树中的节点数量 private transient int size = 0; // 多次在集合类中提到了,用于举了结构行的改变次数 private transient int modCount = 0;2、TreeMap 构造函数
// 构造方法一,默认的构造方法,comparator为空,即采用自然顺序维持TreeMap中节点的顺序public TreeMap() { comparator = null;}// 构造方法二,提供指定的比较器public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator;}// 构造方法三,采用自然序维持TreeMap中节点的顺序,同时将传入的Map中的内容添加到TreeMap中public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m);}/** *构造方法四,接收SortedMap参数,根据SortedMap的比较器维持TreeMap中的节点顺序,* 同时通过buildFromSorted(int size, Iterator it, java.io.ObjectInputStream str, V defaultVal)方* 法将SortedMap中的内容添加到TreeMap中*/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) { }}
构造方法一采用无参构造方法,不指定比较器,这时候,排序的实现要依赖key.compareTo()方法,因此key必须实现Comparable接口,并覆写其中的compareTo方法。
构造方法二采用带比较器的构造方法,这时候,排序依赖该比较器,key可以不用实现Comparable接口。
3、TreeMap 常用方法
3.1 构造TreeMap
// 将map中的全部节点添加到TreeMap中 public void putAll(Map<? extends K, ? extends V> map) { // 获取map的大小 int mapSize = map.size(); // 如果TreeMap的大小是0,且map的大小不是0,且map是已排序的“key-value对” if (size==0 && mapSize!=0 && map instanceof SortedMap) { Comparator c = ((SortedMap)map).comparator(); // 如果TreeMap和map的比较器相等; // 则将map的元素全部拷贝到TreeMap中,然后返回! if (c == comparator || (c != null && c.equals(comparator))) { ++modCount; try { buildFromSorted(mapSize, map.entrySet().iterator(), null, null); } catch (java.io.IOException cannotHappen) { } catch (ClassNotFoundException cannotHappen) { } return; } } // 调用AbstractMap中的putAll(); // AbstractMap中的putAll()又会调用到TreeMap的put() super.putAll(map); }显然,如果Map里的元素是排好序的,就调用buildFromSorted方法来拷贝Map中的元素,而如果Map中的元素不是排好序的,就调用AbstractMap的putAll(map)方法,该方法源码如下
public void putAll(Map<? extends K, ? extends V> m) { for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) put(e.getKey(), e.getValue()); }很明显它是将Map中的元素一个个put(插入)到TreeMap中的,主要因为Map中的元素是无序存放的,因此要一个个插入到红黑树中,使其有序存放,并满足红黑树的性质。
3.2 插入元素
插入操作即对应TreeMap的put方法,put操作实际上只需按照二叉排序树的插入步骤来操作即可,插入到指定位置后,再做调整,使其保持红黑树的特性。put源码的实现:
public V put(K key, V value) { Entry<K,V> t = root; if (t == null) { //如果根节点为null,将传入的键值对构造成根节点(根节点没有父节点,所以传入的父节点为null) root = new Entry<K,V>(key, value, null); size = 1; modCount++; return null; } // 记录比较结果 int cmp; Entry<K,V> parent; // 分割比较器和可比较接口的处理 Comparator<? super K> cpr = comparator; // 有比较器的处理 if (cpr != null) { // do while实现在root为根节点移动寻找传入键值对需要插入的位置 do { // 记录将要被掺入新的键值对将要节点(即新节点的父节点) parent = t; // 使用比较器比较父节点和插入键值对的key值的大小 cmp = cpr.compare(key, t.key); // 插入的key较大 if (cmp < 0) t = t.left; // 插入的key较小 else if (cmp > 0) t = t.right; // key值相等,替换并返回t节点的value(put方法结束) else return t.setValue(value); } while (t != null); } // 没有比较器的处理 else { // key为null抛出NullPointerException异常 if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; // 与if中的do while类似,只是比较的方式不同 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); } // 没有找到key相同的节点才会有下面的操作 // 根据传入的键值对和找到的“父节点”创建新节点 Entry<K,V> e = new Entry<K,V>(key, value, parent); // 根据最后一次的判断结果确认新节点是“父节点”的左孩子还是又孩子 if (cmp < 0) parent.left = e; else parent.right = e; // 对加入新节点的树进行调整 fixAfterInsertion(e); // 记录size和modCount size++; modCount++; // 因为是插入新节点,所以返回的是null return null; }这里的fixAfterInsertion便是节点插入后对树进行调整的方法,这里不做介绍。
3.3 查找元素
final Entry<K,V> getEntry(Object key) { // 如果有比较器,返回getEntryUsingComparator(Object key)的结果 if (comparator != null) return getEntryUsingComparator(key); // 查找的key为null,抛出NullPointerException if (key == null) throw new NullPointerException(); // 如果没有比较器,而是实现了可比较接口 Comparable<? super K> k = (Comparable<? super K>) key; // 获取根节点 Entry<K,V> p = root; // 对树进行遍历查找节点 while (p != null) { // 把key和当前节点的key进行比较 int cmp = k.compareTo(p.key); // key小于当前节点的key if (cmp < 0) // p “移动”到左节点上 p = p.left; // key大于当前节点的key else if (cmp > 0) // p “移动”到右节点上p = p.right; // key值相等则当前节点就是要找的节点 else // 返回找到的节点 return p; } // 没找到则返回null return null;}3.4 删除元素
删除操作及对应TreeMap的deleteEntry方法,deleteEntry方法同样也只需按照二叉排序树的操作步骤实现即可,删除指定节点后,再对树进行调整即可。deleteEntry方法的实现源码如下:
// 删除“红黑树的节点p” private void deleteEntry(Entry<K,V> p) { modCount++; size--; if (p.left != null && p.right != null) { Entry<K,V> s = successor (p); p.key = s.key; p.value = s.value; p = s; } Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { replacement.parent = p.parent; if (p.parent == null) root = replacement; else if (p == p.parent.left) p.parent.left = replacement; else p.parent.right = replacement; p.left = p.right = p.parent = null; if (p.color == BLACK) fixAfterDeletion(replacement); } else if (p.parent == null) { root = null; } else { if (p.color == BLACK) fixAfterDeletion(p); if (p.parent != null) { if (p == p.parent.left) p.parent.left = null; else if (p == p.parent.right) p.parent.right = null; p.parent = null; } } }
4、总结
TreeMap用的没有HashMap那么多,我们有个宏观上的把我和比较即可。
1、TreeMap是根据key进行排序的,它的排序和定位需要依赖比较器或覆写Comparable接口,也因此不需要key覆写hashCode方法和equals方法,就可以排除掉重复的key,而HashMap的key则需要通过覆写hashCode方法和equals方法来确保没有重复的key。
2、TreeMap的查询、插入、删除效率均没有HashMap高,一般只有要对key排序时才使用TreeMap。
3、TreeMap的key不能为null,而HashMap的key可以为null。
5、TreeSet
TreeSet是基于TreeMap实现的,只是对应的节点中只有key,而没有value,因此对TreeMap比较了解的话,对TreeSet的理解就会非常容易。
参考来源:
【Java集合源码剖析】TreeMap源码剖析
TreeMap源码分析——基础分析(基于JDK1.6)
- java容器类---TreeMap、TreeSet
- JAVA集合容器----TreeMap、TreeSet
- java集合类TreeMap和TreeSet
- java集合类TreeMap和TreeSet
- java集合类TreeMap和TreeSet
- java集合类TreeMap和TreeSet
- java集合类TreeMap和TreeSet
- java集合类TreeMap和TreeSet
- java集合类TreeMap和TreeSet
- Java TreeMap/TreeSet
- java TreeMap和TreeSet
- 《java集合》--TreeMap、TreeSet
- java之TreeMap/TreeSet篇
- Java集合类4—HashSet、TreeSet、HashMap、TreeMap介绍
- java集合类深入分析之TreeMap/TreeSet篇
- java集合类深入分析之TreeMap/TreeSet篇
- java集合类TreeMap和TreeSet及红黑树
- java集合类深入分析之TreeMap/TreeSet篇
- 【C++类型转换】static_cast, dynamic_cast, const_cast探讨
- linux 串口编程
- HDU 1069(Monkey and Banana)动态规划
- system(cmd)的使用注意事项
- ListView 关于Adapter 本地文件中解析json数据完整例子
- java容器类---TreeMap、TreeSet
- Java 大牛养成计划
- JNDI (1)
- C语言关键字register、extern、static、一些总结
- 一段代码
- 我的第一篇博客
- 第三方分享步骤整理
- hdu-1711 Number Sequence
- Android 快速开发框架:推荐10个框架:afinal、ThinkAndroid、andBase、KJFrameForAndroid、SmartAndroid、dhroid..