Java集合之TreeSet


1、TreeSet简介   从名字上可以看出,此集合的实现和树结构有关。与HashSet集合类似,TreeSet也是基于Map来实现,其底层结构为红黑树(特殊的二叉查找树);与HashSet不同的是,TreeSet具有排序功能,分为自然排序(123456)和自定义排序两类,默认是自然排序;在程序中,我们可以按照任意顺序将元素插入到集合中,等到遍历时TreeSet会按照一定顺序输出:倒序或者升序;   TreeSet继承AbstractSet,实现NavigableSet, Cloneable, Serializable接口。  
  1. 与HashSet同理,TreeSet继承AbstractSet类,获得了Set集合基础实现操作;
  2. TreeSet实现NavigableSet接口,而NavigableSet又扩展了SortedSet接口。这两个接口主要定义了搜索元素的能力,例如给定某个元素,查找该集合中比给定元素大于、小于、等于的元素集合,或者比给定元素大于、小于、等于的元素个数;简单地说,实现NavigableSet接口使得TreeSet具备了元素搜索功能;
  3. TreeSet实现Cloneable接口,意味着它也可以被克隆;
  4. TreeSet实现了Serializable接口,可以被序列化,可以使用hessian协议来传输;
  具有如下特点:   1、对插入的元素进行排序,是一个有序的集合(主要与HashSet的区别); 2、底层使用红黑树结构,而不是哈希表结构; 3、允许插入Null值; 4、不允许插入重复元素; 5、线程不安全;   2、基本操作  
public class TestTreeSet {
    public static void main(String[] agrs){
        TreeSet treeSet = new TreeSet();
        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 iteratorAesc = treeSet.iterator();
        while(iteratorAesc.hasNext()){
            String str = iteratorAesc.next();
            System.out.println("遍历元素升序:"+str);
        }
        //迭代器遍历:降序
        Iterator 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 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:  
public class TreeSetTest {
    public static void main(String[] agrs){
        naturalSort();
    }
    //自然排序顺序:升序
    public static void naturalSort(){
        TreeSet treeSetString = new TreeSet();
        treeSetString.add("a");
        treeSetString.add("z");
        treeSetString.add("d");
        treeSetString.add("b");
        System.out.println("字母顺序:" + treeSetString.toString());
        TreeSet treeSetInteger = new TreeSet();
        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]
  接下来,我们自定义对象,看能否实现:
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 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接口,在做比较:  
class Location implements  Comparable {
    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 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接口,并重写compare方法;

//自定义location类的比较器:
public class locationComparator implements Comparator {
    public int compare(Location location1, Location location2) {
        int num = location1.getID() - location2.getID();
        return num == 0 ? location1.getName().length() - location2.getName().length() : num;
    }
}
不用在数据类中实现Comparerable接口了,单纯的定义一个比较器类即可:
//自定义location类的比较器:
public class LocationComparator implements Comparator {
    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()方法:

  1. 结果返回大于0时,方法前面的值大于方法中的值;
  2. 结果返回等于0时,方法前面的值等于方法中的值;
  3. 结果返回小于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对象;
public class TreeSet extends AbstractSet implements NavigableSet, Cloneable, java.io.Serializable {
    //TreeSet中保存元素的map对象:
    private transient NavigableMap m;
    //map对象中保存的value:
    private static final Object PRESENT = new Object();
}
  4.2、构造方法
public class TreeSet extends AbstractSet implements NavigableSet, Cloneable, java.io.Serializable
{
    //最底层的构造方法,不对外。传入一个NavigableMap接口的实现类
    TreeSet(NavigableMap m)
    {
        this.m = m;
    }
    //无参构造:向底层构造传入一个TreeMap对象:
    public TreeSet()
    {
        this(new TreeMap());
    }
    //传入比较器的构造:通常传入一个自定义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 s)
    {
        this(s.comparator());
        addAll(s);
    }
}
4.3、添加元素add 向TreeSet中添加元素:
public boolean add(E e) {
    return m.put(e, PRESENT)==null;
}
  4.4、删除元素remove 删除TreeSet中元素o:
public boolean remove(Object o) {
    return m.remove(o)==PRESENT;
}
  5、SortedSet和NavigableSet   在介绍TreeSet的时候都会提到NavigableSet,NavigableSet是个"导航Set集合",提供了一系列"导航"方法。什么是"导航"方法?通过接口的定义,可以看到NavigableSet继承了SortedSet接口,实现了对其的扩展;而通过下面的方法得出NavigableSet实际提供了一系列的搜索匹配元素的功能,能获取到某一区间内的集合元素 :
public interface NavigableSet extends SortedSet {
     
     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 iterator();//返回此set集合的迭代器--升序
     NavigableSet descendingSet();//以倒序的顺序返回此set集合
     Iterator descendingIterator();//返回此set集合的迭代器--倒序
     //返回此set集合的部分元素--从fromElement开始到toElement结束,其中fromInclusive、toInclusive意为返回的集合是否包含头尾元素
     NavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive);
     //返回此set集合的部分元素--小于toElement,inclusive意味返回的集合是否包含toElement
     NavigableSet headSet(E toElement, boolean inclusive);
     //返回此set集合的部分元素--从fromElement开始到toElement结束,包含头不含为尾
     SortedSet subSet(E fromElement, E toElement);
     //返回此set集合的部分元素--小于toElement
     SortedSet headSet(E toElement);
     
     //返回此set集合的部分元素--大于等于toElement
     SortedSet tailSet(E fromElement);
}
  说完了NavigableSet,再看父类SortedSet接口:通过名字,我们可以得出此接口跟排序有关,会提供跟排序的方法;
public interface SortedSet extends Set {
    //返回与排序有关的比较器
    Comparator<? super E> comparator();
    //返回从fromElement到toElement的元素集合:
    SortedSet subSet(E fromElement, E toElement);
    
    //返回从第一个元素到toElement元素的集合:
    SortedSet headSet(E toElement);
  
    //返回从fromElement开始到最后元素的集合:
    SortedSet tailSet(E fromElement);
    
    //返回集合中的第一个元素:
    E first();
   
    //返回集合中的最后一个元素:
    E last();
}