java集合之TreeSet
TreeSet类:位于java.util包下
特点:
1)底层数据结构是红黑树,即平衡二叉树,有序(这里的有序不是list的有序概念),实现非同步,内部功能实现依赖于TreeMap的方法。
2)该类返回的元素顺序并非是集合添加元素的顺序,而是按照某个排序算法排列的。该算法有两种情况,由创建TreeSet实例时所用的构造函数决定使用哪种顺序。
TreeSet存储元素自然排序和唯一的图解:
主要讲一下怎样实现有序的问题:
1 TreeSet实现有序之自然排序:
1)一般系统类:
自然排序:表示添加对象实现java.lang包中定义的Comparable接口。很多情况下类实例的排序方法应该是较为直观合理的。比如,数字包装器类(Integer等)、String类、Date类的自然排序是从最低到最高的。
TreeSet<Integer> set = new TreeSet<Integer>();
set.add(new Integer(100)); //等价于set.add(100);
set.add(new Integer(70));
set.add(new Integer(89));
for(Integer i:set){
System.out.println(i);
}
输出:70
89
100
为什么会按照这种排序方式呢?
这是因为Integer类实现了Comparable接口,因此返回的数据按升序排列。
Comparable接口只定义了一个方法:public int compareTo(Object o):如果调用该方法的对象小于比较对象,那么返回负值,如果大于,则返回正值,如果相等则返回0。
Integer类中实现compareTo方法的源码:
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value); //调用的compare方法判断
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1); //如果x>y返回1,x=y返回0,x<y返回-1
}
2)自定义类:
java中的系统类都需要有意义的、直观的排序,因此都实现了Comparable接口,但是如何处理Student这类自定义类?
如果想要排序Student,只能在Student中实现Comparable接口,指定一种自然排序,那么treeSet就能对Student排序了
Student类:
package com.hzw.set;
public class Student implements Comparable{
private String name;
private int age;
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public int compareTo(Object o) {
if(o != null && o instanceof Student){
Student another = (Student)o;
int first = this.age-another.age;
return first==0?this.name.compareTo(another.name):first; //Student首先按照年龄排序,如果年龄相同再按照姓名排序
}
return 0;
//或者
// if(o==null) return 0;
// if(!(o instanceof Student)) return 0;
// Student ano = (Student)o;
// int result = 0;
// return (result=age-ano.age)==0?name.compareTo(ano.name):result;
}
}
package com.hzw.set;
import java.util.TreeMap;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Student> set1 = new TreeSet<Student>();
Student stu1 = new Student("wen",12);
Student stu2 = new Student("di",15);
Student stu3 = new Student("wen",12);
Student stu4 = new Student("yy",14);
Student stu5 = new Student("we",12);
Student stu6 = new Student("cc",11);
Student stu7 = new Student("aw",10);
set1.add(stu1);
set1.add(stu2);
set1.add(stu3);
set1.add(stu4);
set1.add(stu5);
set1.add(stu6);
set1.add(stu7);
//set1.add(null);
for(Student stu:set1){
System.out.println(stu.getName()+"-----------"+stu.getAge());
}
}
}
输出结果:
aw-----------10
cc-----------11
we-----------12
wen-----------12
yy-----------14
di-----------15
补充:在讲前面几个集合时,他们基本都可以存储null,但是在TreeSet实现自然排序的,也就是本例中,是不能存储null的,会出现空指针异常:
Exception in thread "main" java.lang.NullPointerException
at java.util.TreeMap.put(Unknown Source)
at java.util.TreeSet.add(Unknown Source)
at com.hzw.set.TreeSetDemo.main(TreeSetDemo.java:24)
如果可以解释这个现象只能通过源码来解决:
此处是TreeSet存储数据的源码:
public boolean add(E e) {
return m.put(e, PRESENT)==null; //底层操作依赖于Map集合,即包装了的TreeMap集合
}
通过查看TreeMap的put添加元素的源码:就是一颗二叉树
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key);
root = new Entry<>(key, value, null); //树显然非空,pass
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key); //此处是比较器实现需要考虑的,咱们此时是自然排序,所以pass
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else { //重点分析代码
if (key == null) //这就解释了为什么不能存null值
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key); //这里解释平衡二叉树存取元素的重点,也就是原理
if (cmp < 0) //如果待插入元素小于根节点,则放在根的左子树上,大于放在右子树
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value); //如果等于,则不存储
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
2 TreeSet实现有序之比较器排序:
TreeSet实现比较器排序的方式有两种:匿名内部类方式和外部类方式
2.1 首先介绍下比较器接口:Comparator
该接口有两个方法:
int compare(T o1,To2):如果o1>o2,返回正值;o1<o2,返回负值;o1=o2,返回0;
boolean equals(Object obj):该方法与Object中的equals方法一致
2.2 存储比较器排序方式存储java系统类实例元素
1)比较器的实现方式:匿名内部类方式
package com.hzw.set;
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetDemo2 {
public static void main(String[] args) {
TreeSet<Integer> set1 = new TreeSet<Integer>(new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}});
set1.add(100);
set1.add(89);
set1.add(102);
set1.add(79);
set1.add(99);
set1.add(89);
for(Integer i:set1){
System.out.println(i);
}
}
}
输出结果是:
102
100
99
89
79
2)比较器的实现方式:外部类方式
外部类:MyComparator
package com.hzw.set;
import java.util.Comparator;
public class MyComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
}
package com.hzw.set;
import java.util.TreeSet;
public class TreeSetDemo3 {
public static void main(String[] args) {
MyComparator mc = new MyComparator();
TreeSet<Integer> set1 = new TreeSet<Integer>(mc);
set1.add(100);
set1.add(89);
set1.add(102);
set1.add(79);
set1.add(99);
set1.add(89);
for(Integer i:set1){
System.out.println(i);
}
}
}
输出结果:
102
100
99
89
79
2.3 存储自定义类的实例:其实都是一样的,不讲了吧。。。。