JAVA集合之——TreeSet

来源:互联网 发布:淘宝链接怎么做的 编辑:程序博客网 时间:2024/05/21 10:41

JAVA集合之——TreeSet

    一、TreeSet的排序原理

     TreeSet是有序的,且不可以重复的集合,首先,进行如下测试:

public static void main(String[] args){//1. 创建一个存储Integer类型的TreeSetTreeSet<Integer> set = new TreeSet<Integer>();//无序添加对像set.add(40);set.add(10);set.add(10);set.add(30);set.add(20);//打印//从结果来看数据是有序输出的,并且重复的对像不会被添加//TreeSet是一个Sorted Collectionfor(int x: set){System.out.println(x);}//2. 创建一个存储String类型的TreeSetTreeSet<String> set1 = new TreeSet<>();//无序添加对像set1.add("Hello");set1.add("World");set1.add("World");set1.add("JAVA SE");set1.add("JAVA EE");//打印//打印结果是按字符串排序的,并且得复的元素不会被添加for(String s: set1){System.out.println(s);}}
    经过测试发现,TreeSet对插入的元素进行了排序,并且不可插入重复的元素,那TreeSet是怎样做到的呢?
    对一个自定义的对像Persion进行如添加元素,打印操作,如下:

public static void main(String[] args){//创建一个TreeSetTreeSet<Persion> set = new TreeSet<>();//添加自定义元素set.add(new Persion("张三", 50));set.add(new Persion("李四", 30));set.add(new Persion("麻子", 20));set.add(new Persion("麻子", 40));//打印测试for(Persion p: set){System.out.println(p.getName() + p.getAge());}}
    然而,当运行的时候,出错了,错误信息如下:
    TreeSet.Persion cannot be cast to java.lang.Comparable
    原来,在默认的情况下,TreeSet假定插入的元素产现了Comparable接口,关于Comparable接口,JDK文档抄录如下:

    接口 Comparable<T>
    类型参数:T - 可以与此对象进行比较的那些对象的类型
    接口原型:

public interface Comparable<T>{int compareTo(T other);}
    此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo 方法被称为它的自然比较方法。实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序。 实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。对于上面的Iteger类型按大小排序,对于String类,它的commpareTo方法依据字典序对字符串进行比较。这些类是JAVA标准类库,已经实现了Comparable接口。
    如果要插入自定义对像,就必须实现Comparable接口自定义排序规则,即实现compareTo方法,这个方法的算法如何实现,就看自已的需求了,如果按照年年龄进行排序,那么Persion类的定义如下:

public class Persion implements Comparable<Persion>{private String name;private int   age;public Persion(String name,int age){this.name = name;this.age  = age;}public String getName(){return this.name;}public int getAge(){return this.age;}//自定义类,要实现Comparable接口的compareTo方法public int compareTo(Persion other){return this.age - other.age;}}
    如上代码所示,compareTo方法的实现是依照Persion对像的年龄比较,所以重新运行主程序代码,添加元素后,打印输出是按年龄排序的。
    实际上,在使用TreeSet存储对像时,它的add()方法是会自动的调用compareTo方法来进行对像的比较,如果相同的元素,就不存储,不相同则按照红黑树(一种自平衡的二叉树)的算法进行存储。
    即,如果a和b相等,调用a.compareTo(b)一定返回0;如果排序后a位于b之前,则返回负值;如果a位于b之后,则返回正值。

    对于上面的例子,如果添加一行:
    set.add(new Persion("小明", 50));
    经测试发现这个元素是没有被添加进去的。为什么?
    上面的compareTo方法是按age进行比较的,age相同,则被认为是相同的对像,所以没有添加进去,所以对于compareTo方法的实现方式,是要根据需求来定的,经过更改,我们采用下面的方式,看起来更完善一些。
public int compareTo(Persion other){//首先按age排序int num = this.age - other.age;//如果age相同按name排序if(num == 0){return this.name.compareTo(other.name);}//执行到此表示age不同,返回比较结果return num;}
    经测试,name和age都相同的元素添加不进去,有一个不同的元素可以添加,验证了我们的想法。 


    二、Comparable与Comparator的区别。
    使现Comparable接口的compareTo方法有一定的局限性。对于一个给定的类,只能够实现这个接口一次。对于上面的Persion类,在一个集合中需要按age进行排序, 在另一个集中需要按name进行排序,那要怎么办? 另外,如果Persion类的实现者没有实现Comparable接口中,又该怎么办呢?
这时,Comparator就表现出了极大的灵活性了,TreeSet有一个构造方法:
    TreeSet(Comparator<? super E> comparator) 
         --------- 构造一个新的空 TreeSet,它根据指定比较器进行排序。
    如上面所示,在创建集合时,将自已实现的Comparator对像传递给TreeSet构造器,Comparator也是一个接口,它的完整描述如下:

public interface Comparator<T>{int compare(T a,T b);}
    与compareTo方法一样,如果a位于b之前,compare方法返回负值;如果a和b相等,则返回0;否则返回正值。查JDK文档发现,该接口还有一个equals方法,但是不需要实现。
    通常,使用匿名内部类的方式将实现的Comparator对像传递给TreeSet构造器:
TreeSet<Persion> set = new TreeSet<>(new Comparator<Persion>()        {        public int compare(Persion a, Persion b)        {            //首先按age排序            int num = a.getAge() - b.getAge();                        //如果age相同按name排序            if(num == 0)            {            return a.getName().compareTo(b.getName());            }                        //执行到此表示age不同,返回比较结果            return num;        }        });
    这样的话,对于基本的Persion是固定的,而在不同的集合中却可以使用不同的排序规则对集合元素进行排序,灵活性大大提高。

 

    总结,TreeSet集合保证元素有序和唯一的原理:

    1. 唯一性: 调用add()时通过比较返回是否为0来确确定元素是否相同。

    2. 有序:

        A: 自然排序(元素具备比较性) —— 元素所属的类实现自然排序的Comparable接口。

        B: 比较器排序(集合具备比较性) —— 集合的构造方法接收一个比较器Comparator的子类对像。


    关于HashSet和TreeSet的选择:

    如果不需要对元素进行排序,就没必要付出使用TreeSet所带来的排序开销。


0 0