您的位置:首页 > 编程语言 > Java开发

JAVA集合之——TreeSet

2015-07-01 15:04 465 查看
JAVA集合之——TreeSet
一、TreeSet的排序原理
TreeSet是有序的,且不可以重复的集合,首先,进行如下测试:

public static void main(String[] args)
{
//1. 创建一个存储Integer类型的TreeSet
TreeSet<Integer> set = new TreeSet<Integer>();

//无序添加对像
set.add(40);
set.add(10);
set.add(10);
set.add(30);
set.add(20);

//打印
//从结果来看数据是有序输出的,并且重复的对像不会被添加
//TreeSet是一个Sorted Collection
for(int x: set)
{
System.out.println(x);
}

//2. 创建一个存储String类型的TreeSet
TreeSet<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)
{
//创建一个TreeSet
TreeSet<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所带来的排序开销。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: