java集合(二)TreeSet

前面说的ArraylList集合,是有序可重复的,那么下面介绍的TreeSet是无序不可重复的

Set:无序、不可重复、无下标

1. TreeSet

这个运用到的,就是计算机数据结构中,二叉树了,但其实底层由很多组成

TreeSet是无序不可重复的,不能储存null对象
使用TreeSet存储自定义对象,对象的类必须使用自然排序
或者在建立TreeSet对象时,通过构造方法传递迭代器

TreeSet的构造方法
在这里插入图片描述
集合的用法都大致相同,所以增删改查就不说了,更多的需要自己查api

对于TreeSet来说,最重要最难的点在于自然排序和选择排序

2.自然排序

存储进TreeSet的对象类必须实现Comparable接口,重写CompareTo(),在compareTo()实现比较规则,返回正数,存储的对象则在右子树,返回负数,则在左子树,放回0,则不存储

首先注意几点

  • 第一,TreeSet放入的引用类型是要实现了Comparable接口的,像上面的int型数字,其实是Integer里面已经实现了Comparable接口,类似的还有String、Double等,我们熟知的类型都能直接放入进TreeSet里,因为他们的底层都实现了Comparable接口。
  • 第二,对于实例化Comparable,一定要加上泛型固定它的存储类型,不能像ArrayList一样什么都放进去,不然在排序时候会报错。
    例如,在Integer类型中突然多了一个String,那么就排不了序,报错
  • 第三,像二叉树一样,如果一个值小于上面的值,那么就放到左子树,如果大于上面的值,就放到右子树。在CompareTo里面,就是根据这个判断的,两个数对比,如a1和a2,如果a1-a2<0,那么就是a1比a2小,放左边,反之放右边,通过这些判断结果,由底层代码进行排序【重点】
public class Demo01 {
    public static void main(String[] args) {
        TreeSet<Integer> treeSet = new TreeSet<>();
        treeSet.add(1);
        treeSet.add(2);
        treeSet.add(3);
        treeSet.add(4);
        Iterator it = treeSet.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
    }
}
结果:
1
2
3
4
2.1 TreeSet 底层源码

想要弄懂他是如何进行排序的,就要弄懂它的底层源码
在这里插入图片描述
add为我们插入数据的方法

因为自然排序里,没有传入选择器,所以左下角的if语句就直接去掉了,执行最右边的else语句

最右边的代码就是二叉树运行的逻辑,中间的Entry是二叉树的元素,和右边结合起来看

中间的compare方法是add元素必经之地,它需要带有Compareable的接口的类才能接收,而且不能为空,这就是为什么TreeSet一定要传入带有Compareable且非空的元素的原因(由选择器的除外,下面说)

在中间最下面的CompareTo方法,这解释了为什么TreeSet是不可重复,且如何排序的原因:

  • 在最右边代码里,负责每个结点的通过CompareTo进行对比,如果旁边的节点小,那么返回负数(里面是-1);如果相等,那么返回0,然后去除掉,如果大于,那么返回正数。通过这个方法进行排序的。

如果不想看代码,就看上面这个原因就好了,后面自己实现的Compareable接口也要根据这个定理写。

2.2类的排序

如果建立了一个学生类,我们想把类放入进去,并且按照 id号进行排序,如果两个类的属性都相同,那么就删除一个

引入这个例子更好地了解Compareable接口

Student类(为了简单点,属性就不封装了)

// 注意,Student实现了Compareable接口,且接口泛型为Student
public class Student implements Comparable<Student>{
    private int id;    
    private String name;
    private int age;
    
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    @Override
    public int compareTo(Student o) {
        return this.id-o.id;     //按照从小到大排序
    }
}

this.id就是本类的id,o.id是上一个类的id
测试的main

public class TextStudent {
    public static void main(String[] args) {
        TreeSet<Student> set = new TreeSet<>();
        set.add(new Student(1,"zs",18));
        set.add(new Student(5,"ls",54));
        set.add(new Student(2,"ww",7));
        set.add(new Student(2,"ww",7));
        set.add(new Student(7,"lili",32));
        set.add(new Student(58,"jie",70));

        for (Student stu:set) {
            System.out.println(stu);
        }
    }
}
结果:
Student{id=1, name='zs', age=18}
Student{id=2, name='ww', age=7}
Student{id=5, name='ls', age=54}
Student{id=7, name='lili', age=32}
Student{id=58, name='jie', age=70}

我们可以看到,实现了从小到大的排序,且id相同的都去掉了

那么如果我想要,如果id相同了,再根据年龄来进行排序呢?

把上面Student类代码改为

@Override
       public int compareTo(Student o) {
        //如果id不同
        if(this.id!=o.id){
            return this.id-o.id;
        }else{
            //如果id相同
            return this.age - o.age;
        }
    }

当然,只要你设计得当,也能根据字符串名字进行排序

3选择器

选择器,也就是选择排序了,选择排序的和自然排序的逻辑是一样的

再上面的底层代码图里,左下角的被划掉的if语句,如果添加了选择器,那么就是运行那if里面的代码了(运行被划掉的代码),逻辑其实是一样的

Student类,把Compareable去掉了,由于选择器需要外部来调用Student的属性,所以我把它属性进行封装了(当然,平时也要这么写)

public class Student{
    private int id;
    private String name;
    private int age;
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

}

选择器

//和上面最开始案例一样
public class Selector implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getId() - o2.getId();
    }
}

其中o1代表后面的Student,o2代表前面的Student
测试main

public class TextStudent {
    public static void main(String[] args) {
        TreeSet<Student> set = new TreeSet<>(new Selector());
        set.add(new Student(1,"zs",18));
        set.add(new Student(5,"ls",54));
        set.add(new Student(2,"ww",7));
        set.add(new Student(2,"ww",7));
        set.add(new Student(7,"lili",32));
        set.add(new Student(58,"jie",70));

        for (Student stu:set) {
            System.out.println(stu);
        }
    }
}
结果:
Student{id=1, name='zs', age=18}
Student{id=2, name='ww', age=7}
Student{id=5, name='ls', age=54}
Student{id=7, name='lili', age=32}
Student{id=58, name='jie', age=70}

也可以用匿名内部类来写选择器,这样可以看到匿名内部类的作用的–可以少些一个类

TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
        @Override
         public int compare(Student o1, Student o2) {
             return o1.getId()-o2.getId();
         }
 });
4迭代器

集合的迭代器都是一样的 for(set集合没有), foreach, iterator

因为set集合是无序的,所以用不了for循环

上面用到的foreach是简便的,但作者也是希望能多用熟用 iterator作为迭代器

Iterator<Student> it = set.iterator();
while (it.hasNext()){
      System.out.println(it.next());
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值