TreeSet集合--java进阶day10

1.TreeSet集合

如下图,我们创建了一个TreeSet集合,泛型指定为String,使用add添加字符串,打印该集合,控制台出现的结果已经排好序,并且去重

为了了解清楚TreeSet集合排序的原理,我们编写一个学生类,指定其为TreeSet泛型,然后调用add方法

如上图,程序报错,控制台出现类型转换异常,提示说无法将Student转换为Comparable

我们并没有做类型转换的操作,说明是TreeSet集合内部自行调用了这个操作,那么,先了解Comparable是什么

2.Comparable

由下图可知,Comparable是一个接口,而且还带有泛型,并且实现了这个接口的类可以进行排序

由此可知,为什么刚才程序报错了,我们的Student类没有实现Comparable类,也就无法进行比较

我们让Student实现Comparable接口,Comparable接口有泛型,上次说过泛型接口有两种选择,那该用哪一种呢?

我们翻看api帮助文档,寻找答案

api上写,T是与对象进行比较的对象的类型

我们要让学生和学生比较,不就是学生对象和学生对象比较吗?

由此可知,我们应该使用第一种指定泛型的方式

将T写为Student,那么原本的学生对象将与T(传入的学生对象)进行比较

如图,在实现了Comparable接口后,我们重写了一个comparaeto的方法,里面有个返回值,返回的是0

不了解这个方法有什么用,我们先运行,看控制台会打印什么


...

这次控制台不再报错了,但是明明添加了4名学生,却只有王五被打印了,这又是为什么?

暂时先不解释,我们将返回值换成正数和负数,再试试,看控制台会打印什么

当返回值是正数时(1),控制台按照正序打印了全部学生对象

当返回值是负数时(-1),控制台按照倒序打印了全部学生对象

3.compareto

根据上图结果,我们可以知道,排序的结果和compareto的返回值有关

每当我们调用add方法时,TreeSet集合会自动调用compareto方法

4.TreeSet集合存储元素内部原理

[1].compareto返回值为0

之前说过,TreeSet集合的底层数据结构是红黑树,TreeSet会将红黑树添加节点的规则和compareto方法结合,从而存储元素

--当我们调用add方法时,TreeSet集合内部会自动调用compareto方法,根据该方法的返回值来确定红黑树中的节点该如何走

假设compareto里的返回值是0,此时我们调用add方法,TreeSet自动调用compareto方法

compareto方法里的返回值是0,也就代表红黑树中“一样的不存”

但是王五十分特殊,它作为红黑树的树根,必须添加,因此集合中存入了王五

接着走后面add(张三),集合调用compareto,方法返回值是0,所以不存

.....

以此类推,最后集合里就只有王五被存入,所以打印时也就只有王五

[2].compareto返回值是正数

当compareto返回值是1时,正数代表大,也就是红黑树中的“大的往右走”

添加王五后,走add(张三),自动调用compareto,张三和王五比,返回值是1,说明张三大,张三存到王五右边

接着走add(李四),继续调用compareto方法,李四和王五比,返回值是1,说明李四大

李四往右边走,发现又有一个节点(张三),那就继续调用compareto比较,返回值是正数,说明李四大,所以李四就存在张三右边

然后走add(赵六),调用compareto方法,和树根比完,又碰到节点,接着调compareto,以此反复,最后添加到李四右边

[3].compareto返回值是负数

当compareto返回值是负数时,代表红黑树中“小的往左走”

5.TreeSet集合取出数据原理

取出原理为:左,中,右

先从树根开始,往树根左边取,发现是空的,接着取树根中间(也就是树根自己),取出王五

然后再取树根右边,走到张三这里,取出顺序也是左,中,右。先看张三左边,是空的,然后再看中间,取出张三

接着取右边,走到李四,同样是左,中,右,以此类推,取出TreeSet里所有元素

当compareto返回值是负数时,红黑树中,取出顺序也是左,中,右

先从树根(王五)开始,取左边,走到张三,张三接着取左边,走到李四,李四取左边,走到赵六

赵六取左边发现是空的,然后取中间(赵六自己),再取右边是空的,赵六取完再回到李四,李四也是同样方式,最后取出结果,如下图

6.TreeSet第一种排序--自然排序

刚才说的一切都只是为了告诉我们,如果想让我们的对象进行排序,就需要集中注意力在compareto上,要在里面写逻辑

那么,我们试着让学生对象根据年龄进行比较

之前说过,添加对象时,会自动调用compareto,哪一个对象调用的方法,方法中的this就代表哪一个对象

那该跟谁比呢?注意compareto里面的参数o,这个o就是要比较的学生对象

先进行相减操作,然后右键运行,结果是正序

下图是倒序排列

结论:this-o 为正序,o-this为倒序

TreeSet内部排序具体流程:https://kdocs.cn/l/cvASqRTifX8p?linkname=150997471

7.字符串之间的比较:比大小

上图的排序还存在安全隐患,如果两个学生的年龄一样,那么相减为0就无法存入,所以我们还需添加姓名进行比较

姓名是字符串,字符串自带compareto比较大小--负数代表小,正数代表大

使用三元运算符,如果ageResult为0,说明年龄一样,那么比较姓名,如果ageResult不为0,则返回ageResult

右键运行,即使所有学生年龄相同,也做了排序

如果年龄,姓名都相同,也想做保留,如下图

走到红框,代表姓名年龄都相同,此时我们就随便返回一个数,让其保留,如果不相同就继续使用nameResult

这种情况还是比较少见,了解即可,因为TreeSet集合要有去重的效果,年龄姓名都一样会被去重,对于学生对象,我们应该用id是最好的,这里只是为了讲解详细

8.TreeSet第二种排序--比较器排序

如图,我们在TreeSet集合中传入匿名内部类,重写后的compare方法,o1代表this,o2代表o

里面的逻辑按照正序来写,自然排序里的逻辑是倒序,不在学生类里要用get方法获取,

现在我们既写了自然排序,又写了比较器排序,右键运行看看会执行哪一个排序

如上图,很明显是正序,也就是执行了比较器排序

同时具备比较器排序和自然排序,优先按照比较器规则排序

这种规则的意义是:如果以后使用一个类,该类已经具备自然排序,我们无法更改其源代码,就可以使用比较器排序自定义自己想要的排序

posted @ 2025-03-30 11:34  直実  阅读(15)  评论(0)    收藏  举报