TreeSet&NavigableMap&NavigableSet源码解析 给jdk写注释系列之jdk1.6容器(8)
来源:互联网 发布:淘宝代销靠谱吗 编辑:程序博客网 时间:2024/06/07 04:50
TreeSet是一个有序的Set集合。
既然是有序,那么它是靠什么来维持顺序的呢,回忆一下TreeMap中是怎么比较两个key大小的,是通过一个比较器Comparator对不对,不过遗憾的是,今天仍然不会讲Comparator,但是需要明白的是TreeSet要实现信息也必须依靠于Comparator接口。
关于Set,在前面我们讲过一个HashSet,是不是想起了什么,Set和Map在java中是很神奇的一对东东,是的,是一对,他们都是一对对出现的,就像双胞胎。来看一下这两个容器(是的,容器,我们还是要正规一些,什么双胞胎嘛),Map有HashMap,LinkedHashMap还有TreeMap,那Set呢有HashSet,LinkedHashSet还有TreeSet,很一致是不是。还有一点就是,所有的Set的实现都是依靠于Map的,这一点在HashSet中有讲过,重复一篇Set的实现是利用Map作为底层存储,主要用到Map的key来存储元素。不要问我为什么,也不要问我Set为什么不独立一些。
好了,我们知道了TreeSet和TreeMap一样都是基于红黑树实现,明白了前面的TreeMap原理,TreeSet我都不打算说了。
1.定义
1 public class TreeSet<E> extends AbstractSet<E>2 implements NavigableSet<E>, Cloneable, java.io.Serializable
从定义上可以看出TreeSet继承了AbstractSet抽象类,并实现了NavigableSet、Cloneable,Serializable接口,对于NavigableSet是不是还有些许印象,在TreeMap中出现过一个NavigableMap,它们的的目的都一样,都是为了提供跟搜索相关的接口,具体怎么实现,我们后面看。
不过要先看下NavigableSet的接口定义:
1 public interface NavigableSet<E> extends SortedSet<E> { 2 E lower(E e); 3 E floor(E e); 4 E ceiling(E e); 5 E higher(E e); 6 E pollFirst(); 7 E pollLast(); 8 Iterator<E> iterator(); 9 NavigableSet<E> descendingSet();10 Iterator<E> descendingIterator();11 NavigableSet<E> subSet(E fromElement, boolean fromInclusive,12 E toElement, boolean toInclusive);13 NavigableSet<E> headSet(E toElement, boolean inclusive);14 NavigableSet<E> tailSet(E fromElement, boolean inclusive);15 SortedSet<E> subSet(E fromElement, E toElement);16 SortedSet<E> headSet(E toElement);17 SortedSet<E> tailSet(E fromElement);18 }
我把注释都给删掉了,如果只看接口定义,详细你和我一样看不懂这些接口都是什么意思,不着急,我们下面会细讲。
2.底层存储和构造方法
1 // 底层使用NavigableMap来保存TreeSet的元素2 private transient NavigableMap<E,Object> m;3 4 // Dummy value to associate with an Object in the backing Map5 // 由于Set只使用到了Map的key,所以此处定义一个静态的常量Object类,来充当Map的value6 private static final Object PRESENT = new Object();
我想,对于PRESENT这个常量不用多解释了吧,在HashSet中解释过的。至于这里的NavigableMap是什么东西,下面说。
1 /** 2 * 使用指定的navigable map来构造TreeSet 3 */ 4 TreeSet(NavigableMap<E,Object> m) { 5 this.m = m; 6 } 7 8 /** 9 * 默认构造方法,底层使用TreeMap来存储TreeSet元素10 */11 public TreeSet() {12 this(new TreeMap<E,Object>());13 }14 15 /**16 * 使用指定的构造器,构造一个TreeMap来保存TreeSet的数据17 */18 public TreeSet(Comparator<? super E> comparator) {19 this(new TreeMap<E,Object>(comparator));20 }21 22 /**23 * 构造一个指定Collection参数的TreeSet24 */25 public TreeSet(Collection<? extends E> c) {26 this();27 addAll(c);28 }29 30 /**31 * 构造一个指定SortedMap的TreeSet,根据SortedMap的比较器来来维持TreeSet的顺序32 */33 public TreeSet(SortedSet<E> s) {34 this(s.comparator());35 addAll(s);36 }
有么有很奇怪TreeSet底层用的是NavigableMap来存储数据,而不是直接使用TreeMap,我们知道TreeMap是实现类NavigableMap接口的,所以TreeSet默认构造了一个TreeMap来作为NavigableMap的一个实现类,提供给TreeSet存储数据。那么NavigableMap到底是什么东东呢?
NavigableMap定义:
1 public interface NavigableMap<K,V> extends SortedMap<K,V> { 2 // 获取小于指定key的第一个节点对象 3 Map.Entry<K,V> lowerEntry(K key); 4 5 // 获取小于指定key的第一个key 6 K lowerKey(K key); 7 8 // 获取小于或等于指定key的第一个节点对象 9 Map.Entry<K,V> floorEntry(K key);10 11 // 获取小于或等于指定key的第一个key12 K floorKey(K key);13 14 // 获取大于或等于指定key的第一个节点对象15 Map.Entry<K,V> ceilingEntry(K key);16 17 // 获取大于或等于指定key的第一个key18 K ceilingKey(K key);19 20 // 获取大于指定key的第一个节点对象21 Map.Entry<K,V> higherEntry(K key);22 23 // 获取大于指定key的第一个key24 K higherKey(K key);25 26 // 获取Map的第一个(最小的)节点对象27 Map.Entry<K,V> firstEntry();28 29 // 获取Map的最后一个(最大的)节点对象30 Map.Entry<K,V> lastEntry();31 32 // 获取Map的第一个节点对象,并从Map中移除改节点33 Map.Entry<K,V> pollFirstEntry();34 35 // 获取Map的最后一个节点对象,并从Map中移除改节点36 Map.Entry<K,V> pollLastEntry();37 38 // 返回当前Map的逆序Map集合39 NavigableMap<K,V> descendingMap();40 41 // 返回当前Map中包含的所有key的Set集合42 NavigableSet<K> navigableKeySet();43 44 // 返回当前map的逆序Set集合,Set由key组成45 NavigableSet<K> descendingKeySet();46 47 // 返回当前map中介于fromKey(fromInclusive是否包含)和toKey(toInclusive是否包含) 之间的子map48 NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,49 K toKey, boolean toInclusive);50 51 // 返回介于map第一个元素到toKey(inInclusive是否包含)之间的子map52 NavigableMap<K,V> headMap(K toKey, boolean inclusive);53 54 // 返回当前map中介于fromKey(inInclusive是否包含) 到map最后一个元素之间的子map55 NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);56 57 // 返回当前map中介于fromKey(包含)和toKey(不包含)之间的子map58 SortedMap<K,V> subMap(K fromKey, K toKey);59 60 // 返回介于map第一个元素到toKey(不包含)之间的子map61 SortedMap<K,V> headMap(K toKey);62 63 // 返回当前map中介于fromKey(包含) 到map最后一个元素之间的子map64 SortedMap<K,V> tailMap(K fromKey);65 }
从NavigableMap接口的方法中可以看出,基本上定义的都是一些边界的搜索和查询。当然这些方法是不能实现Set的,再看下NavigableMap的定义,NavigableMap继承了SortedMap接口,而SortedMap继承了Map接口,所以NavigableMap是在Map接口的基础上丰富了这些对于边界查询的方法,但是不妨碍你只是用其中Map中自身的功能。’
下面先来看下TreeSet的基础功能吧:
3.TreeSet的增加和删除
1 /** 2 * 利用NavigableMap的put方法实现add方法 3 */ 4 public boolean add(E e) { 5 return m .put(e, PRESENT)== null; 6 } 7 8 /** 9 * 利用NavigableMap的remove方法实现add方法10 */11 public boolean remove(Object o) {12 return m .remove(o)==PRESENT;13 }14 15 /**16 * 添加一个集合到TreeSet中17 */18 public boolean addAll(Collection<? extends E> c) {19 // Use linear-time version if applicable20 // 如果集合c是SortedSet的子类,并且m是TreeMap的子类,则用下面的方法添加(主要为了检查是否需要重新排序)21 if (m .size()==0 && c.size() > 0 &&22 c instanceof SortedSet &&23 m instanceof TreeMap) {24 SortedSet<? extends E> set = (SortedSet<? extends E>) c;25 TreeMap<E,Object> map = (TreeMap<E, Object>) m;26 // 取出集合c的比较器27 Comparator<? super E> cc = (Comparator<? super E>) set.comparator();28 // 取出当前set的比较器29 Comparator<? super E> mc = map.comparator();30 // 如果上面的两种比较器是同一个的话(==或equals),当然TreeSet和TreeMap默认构造方法比较器都是null,这里也是==的31 if (cc==mc || (cc != null && cc.equals(mc))) {32 // 将集合c在当前set集合顺序的基础上,按顺序插入33 map.addAllForTreeSet(set, PRESENT);34 return true;35 }36 }37 38 // 不需要排序的话就按普通方法,调用父类AbstractCollection的addAll方法(将集合c添加到Set尾部)39 return super.addAll(c);40 }41 42 43 /**44 * 添加一个集合到TreeSet中45 */46 public boolean removeAll(Collection<?> c) {47 boolean modified = false;48 49 // 判断当前TreeSet元素个数和指定集合c的元素个数,目的是减少遍历次数50 if (size() > c.size()) {51 // 如果当前TreeSet元素多,则遍历集合c,将集合c中的元素一个个删除52 for (Iterator<?> i = c.iterator(); i.hasNext(); )53 modified |= remove(i.next());54 } else {55 // 如果集合c元素多,则遍历当前TreeSet,将集合c中包含的元素一个个删除56 for (Iterator<?> i = iterator(); i.hasNext(); ) {57 if (c.contains(i.next())) {58 i.remove();59 modified = true;60 }61 }62 }63 return modified;64 }
4.是否包含
1 /** 2 * 利用TreeMap的containsKey方法实现contains方法 3 */ 4 public boolean contains(Object o) { 5 return m .containsKey(o); 6 } 7 8 /** 9 * 检查是否包含指定集合中所有元素,该方法在AbstractCollection中10 */11 public boolean containsAll(Collection<?> c) {12 // 取得集合c的迭代器Iterator13 Iterator<?> e = c.iterator();14 // 遍历迭代器,只要集合c中有一个元素不属于当前HashSet,则返回false15 while (e.hasNext())16 if (!contains(e.next()))17 return false;18 return true;19 }
5.容量检查
1 /** 2 * Returns the number of elements in this set (its cardinality). 3 * 4 * @return the number of elements in this set (its cardinality) 5 */ 6 public int size() { 7 return map .size(); 8 } 9 10 /**11 * Returns <tt>true</tt> if this set contains no elements.12 *13 * @return <tt> true</tt> if this set contains no elements14 */15 public boolean isEmpty() {16 return map .isEmpty();17 }
可以看到由于TreeSet底层基于TreeMap(默认情况下)实现,在代码层面上来看是非常简单的,但是如果想要透彻的明白TreeSet底层存储及其操作,还是要了解TreeMap底层红黑树的原理。
到这里TreeSet的基本方法就分析完了,下面我们来看下,TreeSet实现于NavigableSet的一些边界搜索方法是怎么实现的。
6.NavigableSet&NavigableMap
如果没想错的话,TreeSet实现于NavigableSet的一些边界搜索方法也是基于NavigableMap实现的,我们随便拿两个方法实现来看一下:
1 public E pollFirst() {2 Map.Entry<E,?> e = m.pollFirstEntry();3 return (e == null)? null : e.getKey();4 }5 6 public E pollLast() {7 Map.Entry<E,?> e = m.pollLastEntry();8 return (e == null)? null : e.getKey();9 }
果然没有猜错,这些方法还是基于NavigableMap实现的,要明白其具体实现代码,我们来看看TreeMap中是怎么实现NavigableMap接口中这些方法的。
1 public Map.Entry<K,V> pollFirstEntry() { 2 // 取得当前Map第一个节点 3 Entry<K,V> p = getFirstEntry(); 4 // 返回一个只包含key、value的简单Entry对象,exportEntry不必深究也很简单 5 Map.Entry<K,V> result = exportEntry(p); 6 // 如果节点不为空,将节点删除 7 if (p != null) 8 deleteEntry(p); 9 return result;10 }11 12 public Map.Entry<K,V> pollLastEntry() {13 // 取得当前Map第一个节点14 Entry<K,V> p = getLastEntry();15 // 返回一个只包含key、value的简单Entry对象,exportEntry不必深究也很简单16 Map.Entry<K,V> result = exportEntry(p);17 // 如果节点不为空,将节点删除18 if (p != null)19 deleteEntry(p);20 return result;21 }22 23 /**24 * Returns the first Entry in the TreeMap (according to the TreeMap's25 * key -sort function). Returns null if the TreeMap is empty.26 */27 final Entry<K,V> getFirstEntry() {28 // 取得根节点29 Entry<K,V> p = root;30 if (p != null)31 // 循环取根节点的left,直到取到最左边的一个节点,也就是取得最小值(红黑树原则最左边最小)32 while (p.left != null)33 p = p. left;34 return p;35 }36 37 /**38 * Returns the last Entry in the TreeMap (according to the TreeMap's39 * key -sort function). Returns null if the TreeMap is empty.40 */41 final Entry<K,V> getLastEntry() {42 // 取得根节点43 Entry<K,V> p = root;44 if (p != null)45 // 循环取根节点的right,直到取到最右边的一个节点,也就是取得最大值(红黑树原则最右边最大)46 while (p.right != null)47 p = p. right;48 return p;49 }
在明白了红黑树的原则之后,这几个取第一个和最后一个的方法看起来还是很简单的,我们再来看下其他方法的实现:
1 public NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, 2 K toKey, boolean toInclusive) { 3 // key越界检查,key怎么越界呢,当然是因为TreMap已经对key排序了,不细看 4 if (!inRange(fromKey, fromInclusive)) 5 throw new IllegalArgumentException( "fromKey out of range" ); 6 if (!inRange(toKey, toInclusive)) 7 throw new IllegalArgumentException( "toKey out of range" ); 8 // 返回AscendingSubMap对象 9 return new AscendingSubMap(m,10 false, fromKey, fromInclusive,11 false, toKey, toInclusive);12 }
AscendingSubMap是NavigableSubMap子类,该构造方法直接调用NavigableSubMap,继续看:
1 static abstract class NavigableSubMap<K,V> extends AbstractMap<K,V> 2 implements NavigableMap<K,V>, java.io.Serializable { 3 /** 4 * The backing map. 5 */ 6 final TreeMap<K,V> m; // 底层使用原始TreeMap提供数据操作 7 8 final K lo, hi; 9 final boolean fromStart, toEnd;10 final boolean loInclusive, hiInclusive;11 12 13 NavigableSubMap(TreeMap<K,V> m,14 boolean fromStart, K lo, boolean loInclusive,15 boolean toEnd, K hi, boolean hiInclusive) {16 if (!fromStart && !toEnd) {17 if (m.compare(lo, hi) > 0)18 throw new IllegalArgumentException( "fromKey > toKey" );19 } else {20 if (!fromStart) // type check21 m.compare(lo, lo);22 if (!toEnd)23 m.compare(hi, hi);24 }25 26 // 记录边界27 this.m = m;28 this.fromStart = fromStart;29 this.lo = lo;30 this.loInclusive = loInclusive;31 this.toEnd = toEnd;32 this.hi = hi;33 this.hiInclusive = hiInclusive;34 }35 ... ...36 ... ...37 38 public final V put(K key, V value) {39 // 边界检查,如果不在边界范围内,则抛出异常40 if (!inRange(key))41 throw new IllegalArgumentException( "key out of range" );42 return m .put(key, value);43 }44 public final V get(Object key) {45 return !inRange(key)? null : m.get(key);46 }47 }
上面的代码比较乱,这里总结一下,subMap这个方法要求返回一个介于fromKey、toKey范围内的字Map。在TreeMap的实现中,是靠一个内部Map的子类NavigableSubMap,这个类将记录fromKey、toKey等,将这个子Map返回后,在操作这个子Map的put、get等操作的时候,都会检查是否在之前的限定内,如果是在限定内则抛出异常,也就是说实际上并不是对原Map的切割负责,底层继续使用原Map,只是给原Map加一个限定条件。
想一想这样做的好处,如果是新创建一个子Map来存限定内的元素,或者复制原Map切割掉限定外的元素,这样的新创建都会在堆内存中申请一份内存空间;而TreeMap这样做,只是在一个类中加了一个指针指向原先的Map,这个指针只分配在栈空间,占用很小的一块内存,这样是不是节省内存空间了呢,虽然其他操作要先检查边界效率会低一些。其实这在设计模式上就叫做代理,实际上NavigableSubMap是TreeMap的一个静态代理类。但是这样存在的一个问题是什么呢,原Map和NavigableSubMap指向的是一块内存,当对NavigableSubMap进行添加、删除等修改操作的时候,实际上原Map也已经变化了。
不知道上面的解释是否看明白,不明白的话去看看这个《jvm内存模型及分配参数》。。。
NavigableMap的其他方法就不去逐一分析,很多都是subMap这个方法的重载方法,或者基于红黑树的查询方法,不明白的话要返回去将TreeMap的分析和红黑树的原理多多看几遍了。
TreeSet&NavigableMap&NavigableSet 完!
0 0
- TreeSet&NavigableMap&NavigableSet源码解析 给jdk写注释系列之jdk1.6容器(8)
- HashSet源码解析&Map迭代器 给jdk写注释系列之jdk1.6容器(6)
- 给jdk写注释系列之jdk1.6容器(10)-Stack&Vector源码解析
- 给jdk写注释系列之jdk1.6容器(1):ArrayList源码解析
- ArrayList源码解析 给jdk写注释系列之jdk1.6容器(1)
- LinkedList源码解析 给jdk写注释系列之jdk1.6容器(2)
- HashMap源码解析 给jdk写注释系列之jdk1.6容器(4)
- LinkedHashMap源码解析 给jdk写注释系列之jdk1.6容器(5)
- TreeMap源码解析 给jdk写注释系列之jdk1.6容器(7)
- PriorityQueue源码解析 给jdk写注释系列之jdk1.6容器(12)
- 给jdk写注释系列之jdk1.6容器(12):PriorityQueue源码解析PriorityQueue是一种什么样的容器呢?看过前面的几个jdk容器分析的话,看到Queue这个单词你一定会,哦~这
- Iterator设计模式 给jdk写注释系列之jdk1.6容器(3)
- JDK之TreeSet源码解析
- Java集合之NavigableMap与NavigableSet接口
- NavigableSet接口源码解析
- NavigableMap与NavigableSet
- NavigableMap与NavigableSet
- NavigableMap与NavigableSet
- 向量vector理解
- HDU 1686 Oulipo
- 五大常用算法之三:贪心算法(转)
- Android:极光推送集成注意要点——解决:Get sdk version fail![获取sdk版本失败!]解决方案
- [编程题]由异或门引出的几道数组的算法题
- TreeSet&NavigableMap&NavigableSet源码解析 给jdk写注释系列之jdk1.6容器(8)
- TensorFlow学习笔记(三):Tutorial第二个例子-深入MNIST-卷积神经网络
- Python基础教程1-3章笔记
- 杭电-4857 逃生(拓扑排序+优先队列)
- 邻接表(链表)存图
- (一)表空间的概念
- kubernetes scheduler 模块分析
- Issure: Windows 80 端口被占用
- 五大常用算法之四:回溯法(转)