目录
TreeSet:能够对元素按照某种规则进行排序。
* 排序有两种方式
* A:自然排序//默认TreeMap的构造器方法
* B:比较器排序//comparator
*
* TreeSet集合的特点:排序和唯一
TreeSet add()方法的JDK源码:
interface Collection {...}
interface Set extends Collection {...}
interface NavigableMap {
}
class TreeMap implements NavigableMap {
//TreeMap存储根据红黑树,其实是二叉树
//该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator //进行排序,具体取决于使用的构造方法。
private final Comparator<? super K> comparator;
//走无参构造
public TreeMap() {
comparator = null;
}
//走带参构造
//构造一个新的、空的树映射,该映射根据给定比较器进行排序。
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
public V put(K key, V value) {//key是要插入进来的值
Entry<K,V> t = root;//树的存储需要根,前边造的是根
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
//Comparator:强行对某个对象 collection 进行整体排序 的比较函数。可以将 Comparator 传递给 sort 方法(如 Collections.sort 或Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用 Comparator 来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序,里边比较的方法是compare()
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
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)
throw new NullPointerException();
//此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 //compareTo 方法被称为它的自然比较方法。
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;
}
}
class TreeSet implements Set {
//默认构造方法构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
//NavigableMap其实是一个接口,如果调用其实调用的是他的实现类TreeMap
private transient NavigableMap<E,Object> m;
public TreeSet() {
//创建实例要调用TreeMap方法的put方法,,接口实例化需要new一个子类对象
this(new TreeMap<E,Object>());
}
public boolean add(E e) {
//put方法是treemap方法里的
return m.put(e, PRESENT)==null;
}
}
通过观察TreeSet的add()方法,我们知道最终要看TreeMap的put()方法。
真正的比较是依赖于元素的compareTo()方法,而这个方法是定义在 Comparable里面的。
所以,要想重写该方法,就必须是先 Comparable接口。这个接口表示的就是自然排序。
TreeSet集合的特点:排序和唯一
自然排序:Comparable
例一:(存储字符串并遍历)
由于Integer类实现了Comparable接口并重写了compareTo()方法,所以满足了集合的排序和唯一
TreeSet<Integer> ts = new TreeSet<Integer>();
// 创建元素并添加
// 20,18,23,22,17,24,19,18,24
ts.add(20);
ts.add(18);
ts.add(23);
ts.add(22);
ts.add(17);
ts.add(24);
ts.add(19);
ts.add(18);
ts.add(24);
// 遍历
for (Integer i : ts) {
System.out.println(i);
}
例二:存储自定义对象
必须实现Comparable并覆盖里边的compareTo()方法
例一:存储学生对象
pojo对象Student类加入以下方法:(0:相等;-1:小于;1:大于)
@Override
public int compareTo(Student s) {
// return 0;
// return 1;
// return -1;
// 这里返回什么,其实应该根据我的排序规则来做
// 按照年龄排序,主要条件
int num = this.age - s.age;
// 次要条件
// 年龄相同的时候,还得去看姓名是否也相同
// 如果年龄和姓名都相同,才是同一个元素
int num2 = num == 0 ? this.name.compareTo(s.name) : num;
return num2;
}
测试类:
TreeSet<Student> ts =new TreeSet<Student>();
Student s1 = new Student("jay", 27);
Student s2 = new Student("jay", 29);
Student s4 = new Student("jay", 27);
Student s5 = new Student("tom", 29);
ts.add(s1);
ts.add(s2);
ts.add(s4);
ts.add(s5);
for (Student s : ts) {
System.out.println(s.getUsername() + "---" + s.getPassword());
}
//去除了用户名和密码都相同的对象
//jay---27
//jay---29
//tom---29
需求一:根据姓名长度来排序
同理,只需要在pojo对象Student类加入以下方法:
@Override
public int compareTo(Student s) {
// 主要条件 姓名的长度
int num = this.name.length() - s.name.length();
// 姓名的长度相同,不代表姓名的内容相同
int num2 = num == 0 ? this.name.compareTo(s.name) : num;
// 姓名的长度和内容相同,不代表年龄相同,所以还得继续判断年龄
int num3 = num2 == 0 ? this.age - s.age : num2;
return num3;
}
比较器排序:comparator
* TreeSet集合保证元素排序和唯一性的原理
* 唯一性:是根据比较的返回是否是0来决定。
* 排序:
* A:自然排序(元素具备比较性)
* 让元素所属的类实现自然排序接口 Comparable
* B:比较器排序(集合具备比较性)
* 让集合的构造方法接收一个比较器接口的子类对象 ComparatorTreeSet的排序方式有构造方法决定
例一:存储自定义对象
Student类不实现Comparator接口,而是定义一个MyComparator类来实现接口,在创建TreeSet树时,把MyComparator对象传递进去。
MyComparator.java
@Override
public int compare(Student s1, Student s2) {
// int num = this.name.length() - s.name.length();
// this -- s1
// s -- s2
// 姓名长度
int num = s1.getName().length() - s2.getName().length();
// 姓名内容
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
// 年龄
int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
return num3;
}
测试类:
第一种写法:
TreeSet<Student> ts = new TreeSet<Student>(); //自然排序
//public TreeSet(Comparator comparator) //比较器排序
TreeSet<Student> ts = new TreeSet<Student>(new MyComparator());
Student s1 = new Student("jay", 27);
Student s2 = new Student("jay", 29);
Student s4 = new Student("jay", 27);
Student s5 = new Student("tom", 29);
ts.add(s1);
ts.add(s2);
ts.add(s4);
ts.add(s5);
for (Student s : ts) {
System.out.println(s.getName() + "---" + s.getAge());
}
第二种:使用匿名内部类
// 如果一个方法的参数是接口,那么真正要的是接口的实现类的对象
// 而匿名内部类就可以实现这个东西
// 此时TreeMap源码中德 private final Comparator<? super K> comparator;不为空,在源码中接下去判断走的是比较器排序
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// 姓名长度
int num = s1.getName().length() - s2.getName().length();
// 姓名内容
int num2 = num == 0 ? s1.getName().compareTo(s2.getName())
: num;
// 年龄
int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
return num3;
}
});
Student s1 = new Student("jay", 27);
Student s2 = new Student("jay", 29);
Student s4 = new Student("jay", 27);
Student s5 = new Student("tom", 29);
ts.add(s1);
ts.add(s2);
ts.add(s4);
ts.add(s5);
for (Student s : ts) {
System.out.println(s.getName() + "---" + s.getAge());
}
需求一:获取10个1至20的随机数,要求随机数不能重复。
/* 分析:
* A:创建随机数对象
* B:创建一个HashSet集合
* C:判断集合的长度是不是小于10
* 是:就创建一个随机数添加
* 否:不搭理它
* D:遍历HashSet集合
*/
public class HashSetDemo {
public static void main(String[] args) {
// 创建随机数对象
Random r = new Random();
// 创建一个Set集合
HashSet<Integer> ts = new HashSet<Integer>();
// 判断集合的长度是不是小于10
while (ts.size() < 10) {
int num = r.nextInt(20) + 1;
ts.add(num);
}
// 遍历Set集合
for (Integer i : ts) {
System.out.println(i);
}
}
}
需求二:学生总分从高到低输出到控制台
/* 分析:
* A:定义学生类
* B:创建一个TreeSet集合
* C:总分从高到底如何实现呢?
* D:键盘录入5个学生信息
* E:遍历TreeSet集合
*/
public class TreeSetDemo {
public static void main(String[] args) {
// 创建一个TreeSet集合
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// 总分从高到低
int num = s2.getSum() - s1.getSum();
// 总分相同的不一定语文相同
int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num;
// 总分相同的不一定数序相同
int num3 = num2 == 0 ? s1.getMath() - s2.getMath() : num2;
// 总分相同的不一定英语相同
int num4 = num3 == 0 ? s1.getEnglish() - s2.getEnglish() : num3;
// 姓名还不一定相同呢
int num5 = num4 == 0 ? s1.getName().compareTo(s2.getName())
: num4;
return num5;
}
});
System.out.println("学生信息录入开始");
// 键盘录入5个学生信息
for (int x = 1; x <= 5; x++) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入第" + x + "个学生的姓名:");
String name = sc.nextLine();
System.out.println("请输入第" + x + "个学生的语文成绩:");
String chineseString = sc.nextLine();
System.out.println("请输入第" + x + "个学生的数学成绩:");
String mathString = sc.nextLine();
System.out.println("请输入第" + x + "个学生的英语成绩:");
String englishString = sc.nextLine();
// 把数据封装到学生对象中
Student s = new Student();
s.setName(name);
s.setChinese(Integer.parseInt(chineseString));
s.setMath(Integer.parseInt(mathString));
s.setEnglish(Integer.parseInt(englishString));
// 把学生对象添加到集合
ts.add(s);
}
System.out.println("学生信息录入完毕");
System.out.println("学习信息从高到低排序如下:");
System.out.println("姓名\t语文成绩\t数学成绩\t英语成绩");
// 遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "\t" + s.getChinese() + "\t"
+ s.getMath() + "\t" + s.getEnglish());
}
}
}