Java编程的逻辑 (44) - 剖析TreeSet
本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http://item.jd.com/12299018.html
41节介绍了HashSet,我们提到,HashSet有一个重要局限,元素之间没有特定的顺序,我们还提到,Set接口还有另一个重要的实现类TreeSet,它是有序的,与HashSet和HashMap的关系一样,TreeSet是基于TreeMap的,上节我们介绍了TreeMap,本节我们来详细讨论TreeSet。
下面,我们先来看TreeSet的用法,然后看实现原理,最后总结分析TreeSet的特点。
基本用法
构造方法
TreeSet的基本构造方法有两个:
- public TreeSet()
- public TreeSet(Comparator<? super E> comparator)
默认构造方法假定元素实现了Comparable接口,第二个使用传入的比较器,不要求元素实现Comparable。
基本例子
TreeSet经常也只是当做Set使用,只是希望迭代输出有序,如下面代码所示:
- Set<String> words = new TreeSet<String>();
- words.addAll(Arrays.asList(new String[]{
- "tree", "map", "hash", "map",
- }));
- for(String w : words){
- System.out.print(w+" ");
- }
输出为:
- hash map tree
TreeSet实现了两点:排重和有序。
如果希望不同的排序,可以传递一个Comparator,如下所示:
- Set<String> words = new TreeSet<String>(new Comparator<String>(){
- @Override
- public int compare(String o1, String o2) {
- return o1.compareToIgnoreCase(o2);
- }});
- words.addAll(Arrays.asList(new String[]{
- "tree", "map", "hash", "Map",
- }));
- System.out.println(words);
忽略大小写进行比较,输出为:
- [hash, map, tree]
需要注意的是,Set是排重的,排重是基于比较结果的,结果为0即视为相同,"map"和"Map"虽然不同,但比较结果为0,所以只会保留第一个元素。
以上就是TreeSet的基本用法,简单易用。不过,因为有序,TreeSet还实现了NavigableSet和SortedSet接口,NavigableSet扩展了SortedSet,此外,TreeSet还有几个构造方法,我们来看下。
高级用法
SortedSet接口
SortedSet接口与SortedMap接口类似,具体定义为:
- public interface SortedSet<E> extends Set<E> {
- Comparator<? super E> comparator();
- SortedSet<E> subSet(E fromElement, E toElement);
- SortedSet<E> headSet(E toElement);
- SortedSet<E> tailSet(E fromElement);
- E first();
- E last();
- }
first()返回第一个元素,last()返回最后一个,headSet/tailSet/subSet都返回一个视图,包括原Set中的一定取值范围的元素,区别在于范围:
- headSet:严格小于toElement的所有元素
- tailSet: 大于等于fromElement的所有元素
- subSet: 大于等于fromElement,且小于toElement的所有元素
与之前介绍的视图概念一样,对返回视图的操作会直接影响原Set。
comparator()返回使用的比较器,如果没有自定义的比较器,返回值为null。
我们来看一段简单的示例代码,以增强直观感受,输出用注释说明:
- SortedSet<String> set = new TreeSet<String>();
- set.addAll(Arrays.asList(new String[]{
- "c", "a", "b", "d","f"
- }));
- System.out.println(set.first()); //a
- System.out.println(set.last()); //f
- System.out.println(set.headSet("b"));//[a]
- System.out.println(set.tailSet("d"));//[d, f]
- System.out.println(set.subSet("b", "e")); //[b, c, d]
- set.subSet("b", "e").clear(); //会从原set中删除
- System.out.println(set); //[a, f]
NavigableSet接口
与NavigableMap类似,NavigableSet接口扩展了SortedSet,主要增加了一些查找邻近元素的方法,比如:
- E floor(E e); //返回小于等于e的最大元素
- E lower(E e); // 返回小于e的最大元素
- E ceiling(E e); //返回大于等于e的最小元素
- E higher(E e); //返回大于e的最小元素
相比SortedSet中的视图方法,NavigableSet增加了一些方法,以更为明确的方式指定返回值中是否包含边界值,如:
- NavigableSet<E> headSet(E toElement, boolean inclusive);
- NavigableSet<E> tailSet(E fromElement, boolean inclusive);
- NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
- E toElement, boolean toInclusive);
NavigableSet也增加了两个对头尾的操作:
- E pollFirst(); //返回并删除第一个元素
- E pollLast(); //返回并删除最后一个元素
此外,NavigableSet还有如下方法,以方便逆序访问:
- NavigableSet<E> descendingSet();
- Iterator<E> descendingIterator();
我们来看一段简单的示例代码,以增强直观感受,输出用注释说明:
- NavigableSet<String> set = new TreeSet<String>();
- set.addAll(Arrays.asList(new String[]{
- "c", "a", "b", "d","f"
- }));
- System.out.println(set.floor("a")); //a
- System.out.println(set.lower("b")); //a
- System.out.println(set.ceiling("d"));//d
- System.out.println(set.higher("c"));//d
- System.out.println(set.subSet("b", true, "d", true)); //[b, c, d]
- System.out.println(set.pollFirst()); //a
- System.out.println(set.pollLast()); //f
- System.out.println(set.descendingSet()); //[d, c, b]
其他构造方法
TreeSet的其他构造方法为:
- public TreeSet(Collection<? extends E> c)
- public TreeSet(SortedSet<E> s)
- TreeSet(NavigableMap<E,Object> m)
前两个都是以一个已有的集合为参数,将其中的所有元素添加到当前TreeSet,区别在于,在第一个中,比较器为null,假定元素实现了Comparable接口,而第二个中,比较器设为和参数SortedSet中的一样。
第三个不是public的,是内部用的。
基本实现原理
41节介绍过,HashSet是基于HashMap实现的,元素就是HashMap中的键,值是一个固定的值,TreeSet是类似的,它是基于TreeMap实现的,我们具体来看一下代码,先看其内部组成。
内部组成
TreeSet的内部有如下成员:
- private transient NavigableMap<E,Object> m;
- private static final Object PRESENT = new Object();
m就是背后的那个TreeMap,这里用的是更为通用的接口类型NavigableMap,PRESENT就是那个固定的共享值。
TreeSet的方法实现主要就是调用m的方法,我们具体来看下。
构造方法
几个构造方法的代码为:
- TreeSet(NavigableMap<E,Object> m) {
- this.m = m;
- }
- public TreeSet() {
- this(new TreeMap<E,Object>());
- }
- public TreeSet(Comparator<? super E> comparator) {
- this(new TreeMap<>(comparator));
- }
- public TreeSet(Collection<? extends E> c) {
- this();
- addAll(c);
- }
- public TreeSet(SortedSet<E> s) {
- this(s.comparator());
- addAll(s);
- }
代码都比较简单,就不解释了。
添加元素
add方法的代码为:
- public boolean add(E e) {
- return m.put(e, PRESENT)==null;
- }
就是调用map的put方法,元素e用作键,值就是固定值PRESENT,put返回null表示原来没有对应的键,添加成功了。
检查是否包含元素
代码为:
- public boolean contains(Object o) {
- return m.containsKey(o);
- }
就是检查map中是否包含对应的键。
删除元素
代码为:
- public boolean remove(Object o) {
- return m.remove(o)==PRESENT;
- }
就是调用map的remove方法,返回值为PRESENT表示原来有对应的键且删除成功了。
子集视图
subSet方法的代码:
- public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
- E toElement, boolean toInclusive) {
- return new TreeSet<>(m.subMap(fromElement, fromInclusive,
- toElement, toInclusive));
- }
先调用subMap方法获取NavigatebleMap的子集,然后调用内部的TreeSet构造方法。
头尾操作
代码为:
- public E first() {
- return m.firstKey();
- }
- public E last() {
- return m.lastKey();
- }
- public E pollFirst() {
- Map.Entry<E,?> e = m.pollFirstEntry();
- return (e == null) ? null : e.getKey();
- }
- public E pollLast() {
- Map.Entry<E,?> e = m.pollLastEntry();
- return (e == null) ? null : e.getKey();
- }
代码都比较简单,就不解释了。
逆序遍历
代码为:
- public Iterator<E> descendingIterator() {
- return m.descendingKeySet().iterator();
- }
- public NavigableSet<E> descendingSet() {
- return new TreeSet<>(m.descendingMap());
- }
也很简单。
实现原理小结
TreeSet的实现代码都比较简单,主要就是调用内部NavigatableMap的方法。
TreeSet特点分析
与HashSet相比,TreeSet同样实现了Set接口,但内部基于TreeMap实现,而TreeMap基于大致平衡的排序二叉树 - 红黑树,这决定了它有如下特点:
- 没有重复元素
- 添加、删除元素、判断元素是否存在,效率比较高,为O(log2(N)),N为元素个数。
- 有序,TreeSet同样实现了SortedSet和NavigatableSet接口,可以方便的根据顺序进行查找和操作,如第一个、最后一个、某一取值范围、某一值的邻近元素等。
- 为了有序,TreeSet要求元素实现Comparable接口或通过构造方法提供一个Comparator对象。
小结
本节介绍了TreeSet的用法和实现原理,在用法方面,它实现了Set接口,但有序,同样实现了SortedSet和NavigatableSet接口,在内部实现上,它使用了TreeMap,代码比较简单。
至此,我们已经介绍完了Java中主要常见的容器接口和实现类,接口主要有队列(Queue),双端队列(Deque),列表(List),Map和Set,实现类有ArrayList, LinkedList, HashMap, TreeMap, HashSet和TreeSet。
关于接口Queue, Deque, Map和Set,Java容器类中还有其他一些实现类,它们各有特点,让我们在接下来的几节中继续探索。
---------------
未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),从入门到高级,深入浅出,老马和你一起探索Java编程及计算机技术的本质。用心原创,保留所有版权。
Java编程的逻辑 (44) - 剖析TreeSet的更多相关文章
- Java编程的逻辑 (51) - 剖析EnumSet
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Java编程的逻辑 (48) - 剖析ArrayDeque
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- 计算机程序的思维逻辑 (44) - 剖析TreeSet
41节介绍了HashSet,我们提到,HashSet有一个重要局限,元素之间没有特定的顺序,我们还提到,Set接口还有另一个重要的实现类TreeSet,它是有序的,与HashSet和HashMap的关 ...
- Java编程的逻辑 (26) - 剖析包装类 (上)
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Java编程的逻辑 (27) - 剖析包装类 (中)
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
- Java编程的逻辑 (28) - 剖析包装类 (下)
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
- Java编程的逻辑 (53) - 剖析Collections - 算法
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Java编程的逻辑 (49) - 剖析LinkedHashMap
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Java编程的逻辑 (46) - 剖析PriorityQueue
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
随机推荐
- Sql 查询当天、本周、本月记录
--查询当天: select * from info where DateDiff(dd,datetime,getdate())=0 --查询24小时内的: select * from info wh ...
- php分享二十五:跨域请求
问题: 跨域请求有几种方式? jsonp支持哪几种请求方式? 支持post请求吗? 支持get请求吗? 由于浏览器同源策略,凡是发送请求url的协议.域名.端口三者之间任意一与当前页面地址不同即为跨域 ...
- 【Unity】第9章 粒子系统
分类:Unity.C#.VS2015 创建日期:2016-05-02 一.简介 粒子是在三维空间中渲染出来的二维图像,主要用于在场景中表现如烟.火.水滴.落叶.--等各种效果. Unity粒子系统 ( ...
- CodeSign error: code signing is required for product type Application in SDK iOS XXX的解决办法
转自:http://www.tuicool.com/articles/jYRNbm 在真机测试的时候往往会突然出现这样一个错误,code signing is required for product ...
- Project Euler:Problem 32 Pandigital products
We shall say that an n-digit number is pandigital if it makes use of all the digits 1 to n exactly o ...
- 浅谈hibernate的sessionFactory和session
一.hibernate是什么? Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库. Hiber ...
- fork failed.: Cannot allocate memory
在做压力测试时候: [root@666 ok]# webbench -c 5000 -t30 http://10.100.0.61/ Webbench - Simple Web Benchmark 1 ...
- 评价linux协议栈tcp实现中的prequeue
https://blog.csdn.net/dog250/article/details/5464513 https://wiki.aalto.fi/download/attachments/7078 ...
- django rest_framework入门二-序列化
在前一节中,我们已经粗略地介绍了rest_framework的作用之一序列化,下面我们将详细探究序列化的使用. 1.新建一个app snippets python manage.py startapp ...
- TCP三次握手,四次分手
1.什么是HTTP连接 http是建立在TCP协议之上的一种应用. 最显著的特点是每次请求,都需要服务器响应,请求结束后,会主动释放连接. 1)在HTTP 1.0中,客户端的每次请求都要建立一次单独的 ...