Java集合之TreeSet
1、TreeSet简介
从名字上可以看出,此集合的实现和树结构有关。与HashSet集合类似,TreeSet也是基于Map来实现,其底层结构为红黑树(特殊的二叉查找树);与HashSet不同的是,TreeSet具有排序功能,分为自然排序(123456)和自定义排序两类,默认是自然排序;在程序中,我们可以按照任意顺序将元素插入到集合中,等到遍历时TreeSet会按照一定顺序输出:倒序或者升序;
TreeSet继承AbstractSet,实现NavigableSet, Cloneable, Serializable接口。
-
与HashSet同理,TreeSet继承AbstractSet类,获得了Set集合基础实现操作;
-
TreeSet实现NavigableSet接口,而NavigableSet又扩展了SortedSet接口。这两个接口主要定义了搜索元素的能力,例如给定某个元素,查找该集合中比给定元素大于、小于、等于的元素集合,或者比给定元素大于、小于、等于的元素个数;简单地说,实现NavigableSet接口使得TreeSet具备了元素搜索功能;
-
TreeSet实现Cloneable接口,意味着它也可以被克隆;
-
TreeSet实现了Serializable接口,可以被序列化,可以使用hessian协议来传输;
具有如下特点:
1、对插入的元素进行排序,是一个有序的集合(主要与HashSet的区别);
2、底层使用红黑树结构,而不是哈希表结构;
3、允许插入Null值;
4、不允许插入重复元素;
5、线程不安全;
2、基本操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | public class TestTreeSet { public static void main(String[] agrs){ TreeSet<String> treeSet = new TreeSet<String>(); System.out.println( "TreeSet初始化容量大小:" +treeSet.size()); //元素添加: treeSet.add( "hello" ); treeSet.add( "!" ); treeSet.add( "a" ); treeSet.add( "new" ); treeSet.add( "world" ); System.out.println( "TreeSet容量大小:" + treeSet.size()); System.out.println( "TreeSet元素顺序为:" + treeSet.toString()); //增加for循环遍历: for (String str:treeSet){ System.out.println( "遍历元素:" +str); } //迭代器遍历:升序 Iterator<String> iteratorAesc = treeSet.iterator(); while (iteratorAesc.hasNext()){ String str = iteratorAesc.next(); System.out.println( "遍历元素升序:" +str); } //迭代器遍历:降序 Iterator<String> iteratorDesc = treeSet.descendingIterator(); while (iteratorDesc.hasNext()){ String str = iteratorDesc.next(); System.out.println( "遍历元素降序:" +str); } //元素获取:实现NavigableSet接口 String firstEle = treeSet.first(); //获取TreeSet头节点: System.out.println( "TreeSet头节点为:" + firstEle); // 获取指定元素之前的所有元素集合:(不包含指定元素) SortedSet<String> headSet = treeSet.headSet( "new" ); System.out.println( "new节点之前的元素为:" +headSet.toString()); //获取给定元素之间的集合:(包含头,不包含尾) SortedSet subSet = treeSet.subSet( "hello" , "new" ); System.out.println( "!--world之间节点元素为:" +subSet.toString()); //集合判断: boolean isEmpty = treeSet.isEmpty(); System.out.println( "TreeSet是否为空:" +isEmpty); boolean isContain = treeSet.contains( "who" ); System.out.println( "TreeSet是否包含who元素:" +isContain); //元素删除: boolean jiaboyanRemove = treeSet.remove( "world" ); System.out.println( "jiaboyan元素是否被删除" +jiaboyanRemove); //集合中不存在的元素,删除返回false boolean whoRemove = treeSet.remove( "who" ); System.out.println( "who元素是否被删除" +whoRemove); //删除并返回第一个元素:如果set集合不存在元素,则返回null String pollFirst = treeSet.pollFirst(); System.out.println( "删除的第一个元素:" +pollFirst); //删除并返回最后一个元素:如果set集合不存在元素,则返回null String pollLast = treeSet.pollLast(); System.out.println( "删除的最后一个元素:" +pollLast); treeSet.clear(); //清空集合: } } 输出: TreeSet初始化容量大小: 0 TreeSet容量大小: 5 TreeSet元素顺序为:[a, hello, new , world, !] 遍历元素:a 遍历元素:hello 遍历元素: new 遍历元素:world 遍历元素:! 遍历元素升序:a 遍历元素升序:hello 遍历元素升序: new 遍历元素升序:world 遍历元素升序:! 遍历元素降序:! 遍历元素降序:world 遍历元素降序: new 遍历元素降序:hello 遍历元素降序:a TreeSet头节点为:a new 节点之前的元素为:[a, hello] !--world之间节点元素为:[hello] TreeSet是否为空: false TreeSet是否包含who元素: false jiaboyan元素是否被删除 true who元素是否被删除 false 删除的第一个元素:a 删除的最后一个元素:! |
3、TreeSet元素排序
在前面的章节,我们讲到了TreeSet是一个有序集合,可以对集合元素排序,其中分为自然排序和自定义排序,那么这两种方式如何实现呢?
首先,我们通过JDK提供的对象来展示,我们使用String、Integer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class TreeSetTest { public static void main(String[] agrs){ naturalSort(); } //自然排序顺序:升序 public static void naturalSort(){ TreeSet<String> treeSetString = new TreeSet<String>(); treeSetString.add( "a" ); treeSetString.add( "z" ); treeSetString.add( "d" ); treeSetString.add( "b" ); System.out.println( "字母顺序:" + treeSetString.toString()); TreeSet<Integer> treeSetInteger = new TreeSet<Integer>(); treeSetInteger.add( 1 ); treeSetInteger.add( 24 ); treeSetInteger.add( 23 ); treeSetInteger.add( 6 ); System.out.println(treeSetInteger.toString()); System.out.println( "数字顺序:" + treeSetString.toString()); } } 输出: 字母顺序:[a, b, d, z] 数字顺序:[ 1 , 6 , 23 , 24 ] |
接下来,我们自定义对象,看能否实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | class Location { private String name; private Integer ID; public Location(String name, Integer ID) { this .name = name; this .ID = ID; } public String getName() { return name; } public void setName(String name) { this .name = name; } public Integer getID() { return ID; } public void setID(Integer ID) { this .ID = ID; } } public class TestTreeSet1 { public static void main(String[] agrs){ customSort(); } //自定义排序顺序:升序 public static void customSort(){ TreeSet<Location> treeSet = new TreeSet<>(); //排序对象: Location location1 = new Location( "a" , 1 ); Location location2 = new Location( "new" , 2 ); Location location3 = new Location( "world" , 3 ); //添加到集合: treeSet.add(location1); treeSet.add(location2); treeSet.add(location3); System.out.println( "TreeSet集合顺序为:" +treeSet); } } 输出报错: Exception in thread "main" java.lang.ClassCastException: java4interview.Location cannot be cast to java.lang.Comparable |
通过查看源码发现,在TreeSet调用add方法时,会调用到底层TreeMap的put方法,在put方法中会调用到compare(key, key)方法,进行key大小的比较;在比较的时候,会将传入的key进行类型强转,所以当我们自定义的App类进行比较的时候,自然就会抛出异常,因为App类并没有实现Comparable接口;
将App实现Comparable接口,在做比较:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | class Location implements Comparable<Location> { private String name; private Integer ID; public Location(String name, Integer ID) { this .name = name; this .ID = ID; } public String getName() { return name; } public void setName(String name) { this .name = name; } public Integer getID() { return ID; } public void setID(Integer ID) { this .ID = ID; } @Override public int compareTo(Location location) { int num = this .name.length() - location.name.length(); return num == 0 ? this .ID - location.ID :num; } @Override public String toString() { return "Location{" + "name='" + name + '\ '' + ", ID=" + ID + '}' ; } } public class TestTreeSet1 { public static void main(String[] agrs){ customSort(); } //自定义排序顺序:升序 public static void customSort(){ TreeSet<Location> treeSet = new TreeSet<>(); //排序对象: Location location1 = new Location( "a" , 10 ); Location location2 = new Location( "new" , 4 ); Location location3 = new Location( "world" , 3 ); Location location4 = new Location( "hello" , 5 ); //添加到集合: treeSet.add(location1); treeSet.add(location2); treeSet.add(location3); treeSet.add(location4); System.out.println(treeSet); } } 输出: [Location{name= 'a' , ID= 10 }, Location{name= 'new' , ID= 4 }, Location{name= 'world' , ID= 3 }, Location{name= 'hello' , ID= 5 }] |
此外,还有另一种方式,那就是实现Comparetor<t>接口,并重写compare方法;
1 2 3 4 5 6 7 | //自定义location类的比较器: public class locationComparator implements Comparator<Location> { public int compare(Location location1, Location location2) { int num = location1.getID() - location2.getID(); return num == 0 ? location1.getName().length() - location2.getName().length() : num; } } |
不用在数据类中实现Comparerable接口了,单纯的定义一个比较器类即可:
1 2 3 4 5 6 7 | //自定义location类的比较器: public class LocationComparator implements Comparator<Location> { public int compare(Location location1, Location location2) { int num = location1.getID() - location2.getID(); return num == 0 ? location1.getName().length() - location2.getName().length() : num; } } |
关于compareTo()、compare()方法:
- 结果返回大于0时,方法前面的值大于方法中的值;
- 结果返回等于0时,方法前面的值等于方法中的值;
- 结果返回小于0时,方法前面的值小于方法中的值;
4、TreeSet源码分析
与HashSet类似,TreeSet底层也是采用了一个Map来保存集合元素,这个Map就是NavigableMap。不过,NavigableMap仅仅是一个接口,具体的实现还是使用了TreeMap类;
4.1成员变量
当你看到下面的代码之后,你就会明白我为什么一直在说TreeSet底层使用了Map集合了;成员变量m是一个NavigableMap类型的Map集合,常用实现是TreeMap对象;在TreeMap中,key是我们TreeSet插入的元素,而value则是TreeSet中另一个成员变量PRESENT,一个普通的不能再普通的Object对象;
1 2 3 4 5 6 | public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable { //TreeSet中保存元素的map对象: private transient NavigableMap<E,Object> m; //map对象中保存的value: private static final Object PRESENT = new Object(); } |
4.2、构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable { //最底层的构造方法,不对外。传入一个NavigableMap接口的实现类 TreeSet(NavigableMap<E,Object> m) { this .m = m; } //无参构造:向底层构造传入一个TreeMap对象: public TreeSet() { this ( new TreeMap<E,Object>()); } //传入比较器的构造:通常传入一个自定义Comparator的实现类; public TreeSet(Comparator<? super E> comparator) { this ( new TreeMap<>(comparator)); } //将集合Collection传入TreeSet中: public TreeSet(Collection<? extends E> c) { this (); addAll(c); } //将集合SortedSet传入TreeSet中: public TreeSet(SortedSet<E> s) { this (s.comparator()); addAll(s); } } |
4.3、添加元素add
向TreeSet中添加元素:
1 2 3 | public boolean add(E e) { return m.put(e, PRESENT)== null ; } |
4.4、删除元素remove
删除TreeSet中元素o:
1 2 3 | public boolean remove(Object o) { return m.remove(o)==PRESENT; } |
5、SortedSet和NavigableSet
在介绍TreeSet的时候都会提到NavigableSet,NavigableSet是个"导航Set集合",提供了一系列"导航"方法。什么是"导航"方法?通过接口的定义,可以看到NavigableSet继承了SortedSet接口,实现了对其的扩展;而通过下面的方法得出NavigableSet实际提供了一系列的搜索匹配元素的功能,能获取到某一区间内的集合元素 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public interface NavigableSet<E> extends SortedSet<E> { E lower(E e); //返回此set集合中小于e元素的最大元素 E floor(E e); //返回此set集合中小于等于e元素的最大元素 E ceiling(E e); //返回此set集合中大于等于e元素的最小元素 E higher(E e); //返回此set集合中大于e元素的最小元素 E pollFirst(); //获取并移除此set集合中的第一个元素 E pollLast(); //获取并移除此set集合中的最后一个元素 Iterator<E> iterator(); //返回此set集合的迭代器--升序 NavigableSet<E> descendingSet(); //以倒序的顺序返回此set集合 Iterator<E> descendingIterator(); //返回此set集合的迭代器--倒序 //返回此set集合的部分元素--从fromElement开始到toElement结束,其中fromInclusive、toInclusive意为返回的集合是否包含头尾元素 NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive); //返回此set集合的部分元素--小于toElement,inclusive意味返回的集合是否包含toElement NavigableSet<E> headSet(E toElement, boolean inclusive); //返回此set集合的部分元素--从fromElement开始到toElement结束,包含头不含为尾 SortedSet<E> subSet(E fromElement, E toElement); //返回此set集合的部分元素--小于toElement SortedSet<E> headSet(E toElement); //返回此set集合的部分元素--大于等于toElement SortedSet<E> tailSet(E fromElement); } |
说完了NavigableSet,再看父类SortedSet接口:通过名字,我们可以得出此接口跟排序有关,会提供跟排序的方法;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public interface SortedSet<E> extends Set<E> { //返回与排序有关的比较器 Comparator<? super E> comparator(); //返回从fromElement到toElement的元素集合: SortedSet<E> subSet(E fromElement, E toElement); //返回从第一个元素到toElement元素的集合: SortedSet<E> headSet(E toElement); //返回从fromElement开始到最后元素的集合: SortedSet<E> tailSet(E fromElement); //返回集合中的第一个元素: E first(); //返回集合中的最后一个元素: E last(); } |
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术