Java中Set系列集合

Set集合

特点:添加的元素是无序、不重复、无索引

无序:存和取的顺序有可能是不一样的
不重复:元素不能重复
无索引:不能通过索引获取集合中的元素

Set集合有三个实现类,分别为:

HashSet:无序、不重复、无索引

LinkedHashSet:有序、不重复、无索引 

TreeSet:可排序、不重复、无索引

Set接口中的方法基本上与Collection的API一致

常见方法:
public boolean add(E e) 把给定的对象添加到当前集合中
public void clear() 清空集合中所有的元素
public boolean remove(E e) 把给定的对象在当前集合中删除
public boolean contains(Object obj) 判断当前集合中是否包含给定的对象
public boolean isEmpty() 判断当前集合是否为空
public int size() 返回集合中元素的个数/集合的长度

遍历方式:迭代器遍历、增强for遍历、Lambda遍历

集合特性

接下来演示Set集合的特性:

不重复:如下代码是使用Set集合中的add方法,由于Set集合中的元素是不能重复的,所以第二次添加"aaa"时返回false添加失败

public static void main(String[] args) {
        /*
            利用Set系列的集合,添加字符串,并使用多种方式遍历
            1.迭代器
            2.增强for
            3.Lambda表达式
         */

        //创建一个Set集合对象并添加元素,由于Set本身是一个接口,所以需要用多态的方式创建
        Set<String> s = new HashSet<>();

        boolean r1 = s.add("aaa");
        boolean r2 = s.add("aaa");

        //打印r1和r2添加的结果,打印集合
        System.out.println(r1);//结果:true
        System.out.println(r2);//结果:false
        System.out.println(s);//结果:aaa

    }

无序:如下代码依次添加aaa、bbb、ccc、ddd后,输出的结果确不是同一顺序,因为Set集合具有无序性

public static void main(String[] args) {
        /*
            利用Set系列的集合,添加字符串,并使用多种方式遍历
            1.迭代器
            2.增强for
            3.Lambda表达式
         */

        //创建一个Set集合对象并添加元素,由于Set本身是一个接口,所以需要用多态的方式创建
        Set<String> s = new HashSet<>();

        s.add("aaa");
        s.add("bbb");
        s.add("ccc");
        s.add("ddd");

        //打印集合
        System.out.println(s);//结果:[aaa, ccc, bbb, ddd]
    }

遍历集合

参考如下代码:分别为迭代器遍历、增强for遍历、Lambda表达式遍历

public static void main(String[] args) {
        /*
            利用Set系列的集合,添加字符串,并使用多种方式遍历
            1.迭代器
            2.增强for
            3.Lambda表达式
         */

        //创建一个Set集合对象并添加元素,由于Set本身是一个接口,所以需要用多态的方式创建
        Set<String> s = new HashSet<>();

        s.add("aaa");
        s.add("bbb");
        s.add("ccc");
        s.add("ddd");

        //迭代器遍历
        Iterator<String> it = s.iterator();
        while (it.hasNext()){
            String str = it.next();
            System.out.println(str);
            /*
            结果:
            aaa
            ccc
            bbb
            ddd
             */
        }

        //增强for遍历
        for (String str : s) {
            System.out.println(str);//结果同上
        }

        //Lambda表达式遍历
        s.forEach(str -> System.out.println(str));//结果同上
    }

HashSet底层原理

HashSet里面没有什么新增的方法,所以我们只需要理解一下它的底层原理

HashSet集合底层采取哈希表存储数据

哈希表是一种对于增删改查数据性能都较好的结构

哈希表组成

JDK8之前:数组+链表

JDK8开始:数组+链表+红黑树

在哈希表中有一个非常重要的值:哈希值

哈希值

哈希值:对象的整数表现形式

1.根据hashCode方法算出来的int类型的整数

2.该方法定义在Object类中,所有方法都可以调用,默认使用地址值进行计算

3.一般情况下,会重写hashCode方法,利用对象内部的属性计算哈希值

哈希表在底层是有数组存在的,开始里面存储都是null

如果此时要添加一个数据,它不是从0索引开始存储的,而是按照以下公式存储:

int index = (数组长度 - 1) & 哈希值;

对象的哈希值特点:

1.如果没有重写hashCode方法,那么此时用的是Object计算的,不同对象计算出的哈希值是不同的

2.如果已经重写了hashCode方法,那么不同的对象只要属性值相同,计算出的哈希值就是一样的

3.在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能是一样的(称为:哈希碰撞)举例:int类型的范围是-21亿多-21亿多,如果此时有50个亿的对象,再获取50个亿的对象的哈希值,那么就有8亿多哈希值碰撞。

理解:

接下来演示特点1

参考如下代码:先创建Student对象,此时没有重写hashCode方法,所以俩个相同属性值的不同对象哈希值不同

package com.hao.set;

public class Student {
    private String name;
    private int age;


    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }



    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}
package com.hao.set;

public class A1_HashSetDemo1 {
    public static void main(String[] args) {
        /*
            哈希值:对象的整数表现形式
                    1.根据hashCode方法算出来的int类型的整数
                    2.该方法定义在Object类中,所有方法都可以调用,默认使用地址值进行计算
                    3.一般情况下,会重写hashCode方法,利用对象内部的属性计算哈希值

         */

        //1.创建对象
        Student s1 = new Student("zhangsan", 18);
        Student s2 = new Student("zhangsan", 18);

        //2.如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
        System.out.println(s1.hashCode());//结果:1324119927
        System.out.println(s2.hashCode());//结果:990368553
    }
}

接下来演示特点2

参考如下代码:此时重写了Student方法,所以相同属性值的不同对象哈希值相等

package com.hao.set;

import java.util.Objects;

public class Student {
    private String name;
    private int age;


    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}
package com.hao.set;

public class A1_HashSetDemo1 {
    public static void main(String[] args) {
        /*
            哈希值:对象的整数表现形式
                    1.根据hashCode方法算出来的int类型的整数
                    2.该方法定义在Object类中,所有方法都可以调用,默认使用地址值进行计算
                    3.一般情况下,会重写hashCode方法,利用对象内部的属性计算哈希值

         */

        //1.创建对象
        Student s1 = new Student("zhangsan", 18);
        Student s2 = new Student("zhangsan", 18);

        //2.重写了hashCode方法,不同对象但属性值相同计算出的哈希值是相同的
        System.out.println(s1.hashCode());//结果:-1461067297
        System.out.println(s2.hashCode());//结果:-1461067297
    }
}

接下来演示特点3

其实String中底层已经重写了HashSet方法,并且是按照String的属性值计算的哈希值

参考如下代码:

System.out.println("abc".hashCode());//结果:96354
System.out.println("acD".hashCode());//结果:96354

底层原理 

1.首先创建一个数组,默认长度为16,默认因子0.75,数组中元素的值为null

2.根据元素的哈希值根数组的长度计算出应存入的位置

int index = (数组长度 - 1) & 哈希值;

3.判断当前位置是否为null,如果是null直接存入

-假设此时要存入一个元素,应存入的索引为4

-再存入一个元素,应存入的索引是1。那么这俩个元素都直接存入数组

4.如果当前位置不为null,表示有元素,则调用equals方法比较属性值

-假设此时再次存入一个元素,应存入的索引也是4

5.属性值一样:不存        不一样:存入数组,形成链表

JDK8以前:新元素存入数组,老元素挂在新元素下面

        老元素挂到新元素下面,新元素存入数组,形成链表

JDK8开始:新元素直接挂在老元素下面

        新元素直接挂在老元素下面,形成链表

-此时再存入一个元素,假设此时该元素也需存入4索引。

此时就会在这个链表中,用equals方法依次比较链表上的元素

如果根这个链表中全部元素都和新元素不相等,那么挂在链表后面

-此时再存入一个元素,依旧也需要存入4索引。

此时就会在这个链表中,用equals方法依次比较链表上的元素

假设该元素与链表上第三个元素相等,当前元素就会舍弃不存,不会往集合中添加

JDK8以前的底层原理

6.当数组里面存了16*0.75=12时,那么将原数组扩容到原先的2倍

JDK8开始的底层原理

6.当数组里面存了16*0.75=12时,那么将原数组扩容到原先的2倍

7.当链表长度大于8而且长度大于等于64,那么当前链表就会自动转为红黑树

HashSet集合的三个问题

问题1:为什么存和取顺序不一样?

例如下图:

哈希数组是由一条条链表组成的

1.遍历0索引,为null,跳过

2.遇到链表,遍历链表所有元素(如果遇到红黑树,也会遍历全部元素)

3.......

但是存入时是按照哈希值存入的,所以这个顺序遍历哈希表是无序的

问题2:为什么没有索引?

因为HashSet由数组、链表、红黑树组成,用索引机制显得不合理

问题3:利用什么机制保证数据去重的?

利用上述的HashCode方法、equals方法

如果哈希集合中存储的是自定义对象,一定要重写HashCode和equals方法

练习

接下来看一个练习

package com.hao.test.hashset;

import com.hao.set.Student;

import java.util.HashSet;
import java.util.Iterator;

public class Test {
    public static void main(String[] args) {
        /*
        需求:
        创建一个存储学生对象的集合,存储多个学生对象
        使用程序实现在控制台遍历该集合
        要求:学生对象的成员变量值相同,我们就认为是同一个对象
         */

        //创建学生对象
        Student s1 = new Student("张三", 23);
        Student s2 = new Student("张三", 23);
        Student s3 = new Student("里斯", 21);
        Student s4 = new Student("王五", 21);

        //创建集合用来添加学生
        HashSet<Student> hs = new HashSet<>();
        hs.add(s1);
        hs.add(s2);
        hs.add(s3);
        hs.add(s4);

        //遍历集合并输出
        System.out.println(hs);//结果:[Student{name = 张三, age = 23}, Student{name = 王五, age = 21}, Student{name = 里斯, age = 21}]
    }
}

LinkedHashSet

HashSet的子类

如下图所示:

例如此时先存入蓝色,再存入黄色

第一个蓝色,会记录第二个黄色的地址值

第二个黄色,也会记录第一个蓝色的地址

就会形成一个双向链表

第三个浅蓝色,存入3索引,挂在黄色下面,形成链表

再与第二个黄色互相记录地址值,形成双向链表

再添加第四个,也互相记录地址值

添加规则跟HashSet相同,只是多了个双向链表记录地址值

最后结果如下图:

此时蓝色是头节点

深蓝色是尾节点

此时遍历就不是从0索引开始

而是遍历双向链表

所以形成有序

TreeSet

没有新增方法,直接使用Collection接口中的方法就可以

特点:不重复、无索引、可排序

可排序:按照元素的默认顺序(由小到大)排序

TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好

排序:

1.对于数值类型,Integer、Double,默认按照从小到大的顺序进行排序

2.对于字符、字符串类型:按照ASCII码表中的数字升序进行排序

3.对于自定义对象的排序

例1:

/*
        需求:利用TreeSet存储整数并进行排序
         */

        //1.创建TreeSet集合对象并添加数据
        TreeSet<Integer> ts = new TreeSet<>();

        ts.add(2);
        ts.add(1);
        ts.add(5);
        ts.add(4);
        ts.add(3);
        ts.add(6);

        //2.打印集合
        System.out.println(ts);     //结果:[1, 2, 3, 4, 5, 6]

同样,可以遍历集合,如下代码:

 /*
        需求:利用TreeSet存储整数并进行排序
         */

        //1.创建TreeSet集合对象并添加数据
        TreeSet<Integer> ts = new TreeSet<>();

        ts.add(2);
        ts.add(1);
        ts.add(5);
        ts.add(4);
        ts.add(3);
        ts.add(6);

        //2.遍历集合
        //迭代器
        Iterator<Integer> it = ts.iterator();
        while (it.hasNext()){
            int number = it.next();
            System.out.println(number);
        }
        /*
        结果:
        1
        2
        3
        4
        5
        6
         */

        //增强for
        for (Integer t : ts) {
            System.out.println(t);
        }//结果同上

        //Lambda
        ts.forEach((Integer num) -> System.out.println(num));//结果同上

例2:下图有5个字母和对应的ASCII码值,排序后结果就是:abcde

如果是字符串,且里面的字符比较多,长度不一怎么办?例如下图:

那么此时就是从首字母开始比较,跟字符串的长度无关,举例:

“aaa”和“ab”比较

1.首先比较第一个字母,97=97,继续比较下一个

2.比较第二个字母,98>97,所以aaa要在ab的前面

.......

最后的结果aaa-ab-aba-cd-qwer

例3:

先看如下代码,此时没有指定比较规则,代码报错

package com.hao.treeset;

public class Student {
    private String name;
    private int age;


    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}
/*
        需求:创建一个TreeSet集合,并添加3个学生对象
        学生对象属性;
            姓名,年龄
            要求按照学生的年龄进行排序
            同年龄按照姓名字母排序(暂不考虑中文)
            同姓名,同年龄认为是一个人

        比较规则:
        方式一:
        默认排序/自然排序:Student类实现Comparable接口指定比较规则
         */

        //创建三个学生对象
        Student s1 = new Student("zhangsan", 23);
        Student s2 = new Student("lisi", 24);
        Student s3 = new Student("wangwu", 25);

        //创建集合并添加对象
        TreeSet<Student> ts = new TreeSet<>();

        ts.add(s3);
        ts.add(s2);
        ts.add(s1);

        //遍历集合
        System.out.println(ts);

报错代码:

Exception in thread "main" java.lang.ClassCastException: class com.hao.treeset.Student cannot be cast to class java.lang.Comparable (com.hao.treeset.Student is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
	at java.base/java.util.TreeMap.compare(TreeMap.java:1569)
	at java.base/java.util.TreeMap.addEntryToEmptyMap(TreeMap.java:776)
	at java.base/java.util.TreeMap.put(TreeMap.java:785)
	at java.base/java.util.TreeMap.put(TreeMap.java:534)
	at java.base/java.util.TreeSet.add(TreeSet.java:255)
	at com.hao.treeset.A02_TreeSet.main(A02_TreeSet.java:24)

那么如何指定比较规则呢?

首先我们学习TreeSet的俩种比较方式

1.默认排序/自然排序:Javabean类实现Comparable接口指定比较规则

2.比较器排序:创建TreeSet对象时,传递比较器Comparator指定规则

使用原则:默认使用第一种,如果第一种不能满足当前需求,就使用第二种

第一种比较规则

理解:

在Student实现接口Comparable,重写方法如下:、

再次运行就会按照23,24,25的年龄排序

@Override
    public int compareTo(Student o) {
        //指定排序规则
        //例如:只看年龄,我想要按照年龄升序进行排序
        int result = this.getAge() - o.getAge();
        return result;
    }

如何理解呢?看下述图片

首先需要理解排序规则中的this和o以及返回值

this:表示当前要添加的元素

o:表示已经在红黑树中添加的元素

返回值:<0:当前要添加的元素是小的,存左边

              >0:当前要添加的元素是大的,存右边

              =0:当前要添加的元素已经存在,舍弃

演示这三个Student对象如何存入TreeSet集合:

1.代码首先添加s3,所以先将"wangwu", 25这个对象放到红黑树中作为根节点

2.接着添加s2,"lisi", 24,因为它的年龄24<25,它应该存在s3的左子节点,但程序是怎么判断的呢?

此时就需要看排序规则:this.age()(当前要添加的元素,age=24),o.age()(已经在红黑树中存在的元素,age=25),返回值为24-25=-1

根据:<0:当前要添加的元素是小的,存左边,此时红黑树如下图:

3.接着添加s3,"zhangsan", 23,因为它的年龄23<25,它应该存入s3的左子节点,但这个位置已经有元素s2了,所以需要跟s2进行比较,因为它的年龄23<24,它最后应该存入s2的左子节点,那么程序是如何判断的呢?

此时就需要看排序规则:this.age()(当前要添加的元素,age=23),o.age()(已经在红黑树中存在的元素,age=25),返回值为23-25=-2

根据:<0:当前要添加的元素是小的,存左边

但是因为这个位置已经有元素了,所以程序会再次调用compareTo方法再来比较

this.age()(当前要添加的元素,age=23),o.age()(已经在红黑树中存在的元素,age=24),返回值为23-24=-1

根据:<0:当前要添加的元素是小的,存左边,此时红黑树如下图:

此时违背了红黑规则,根据修改规则,最后红黑树如下图:

下面为在重写规则代码中新增打印代码,方便理解,代码如下:

@Override
    public int compareTo(Student o) {
        //指定排序规则
        //例如:只看年龄,我想要按照年龄升序进行排序
        int result = this.getAge() - o.getAge();
        System.out.println("this.getAge:" + this.getAge() + "o.getAge:" + o.getAge());
        return result;
    }

运行结果如下:

this.getAge:25o.getAge:25
this.getAge:24o.getAge:25
this.getAge:23o.getAge:25
this.getAge:23o.getAge:24

第一次由于红黑树中没有内容,所以把s3自己当作根节点比较

第二种比较规则

比较器排序:创建TreeSet对象时,传递比较器Comparator指定规则

先演示一段代码:

结果是从首字母开始比较ASCII码值排序,为什么是这样呢?

因为String中也实现了Comparable接口,并重写了方法

 /*
    需求:请自行选择比较器排序和自然排序俩种方式
    要求:存入四个字符串,"c", "ab", "df", "qwer"
    按照长度排序,如果长度一样则按照首字母排序
     */

        //1.创建集合并添加元素
        TreeSet<String> ts = new TreeSet<>();

        ts.add("c");
        ts.add("ab");
        ts.add("df");
        ts.add("qwer");

        //2.输出集合
        System.out.println(ts);//结果:[ab, c, df, qwer]

而想要完全需求,需要取String类中修改源码,过于繁琐,所以采取第二种排序方式:比较器

接下来要学习一个新的构造方法:

构造方法:

TreeSet(Comparator<? super E> Comparator)      

构造一个新的空TreeSet,它根据指定比较器排序

因为Comparator是一个接口,需要重写它的方法

看如下代码:

 /*
    需求:请自行选择比较器排序和自然排序俩种方式
    要求:存入四个字符串,"c", "ab", "df", "qwer"
    按照长度排序,如果长度一样则按照首字母排序

    采取第二种排序方式:比较器排序
     */

        //1.创建集合并添加元素
        //o1:当前要添加的元素
        //o2:表示已经在红黑树存在元素
        //返回值规则和之前一样
        TreeSet<String> ts = new TreeSet<>((o1, o2) -> {
                //按照长度排序,下述代码表示,如果长度相同,运行默认的比较方法,否则直接返回
                int i = o1.length() - o2.length();
                i = i == 0 ? o1.compareTo(o2) : i;
                return i;
            }
        );

        ts.add("c");
        ts.add("ab");
        ts.add("df");
        ts.add("qwer");

        //2.输出集合
        System.out.println(ts);//结果:[c, ab, df, qwer]

综合题

接下来看一个练习:需求:创建五个学生对象

属性:姓名、年龄、语文成绩、数学成绩、英语成绩

按照总分从高到低输出到控制台

如果总分一样,按照语文成绩排 如果语文一样,按照数学成绩排 如果数学一样,按照英语成绩排 如果英语一样,按照年龄排 如果年龄一样,按照姓名的字母顺序排 如果都一样,认为是同一个学生,不存 在遍历集合时,看到总分

看如下代码:类中重写了toString方法,使得遍历时可以输出总分

                      类中实现Comparable接口,使得完成排序要求

package com.hao.treeset;

public class StudentText implements Comparable<StudentText>{
    //姓名
    private String name;
    //年龄
    private int age;
    //语文
    private int chinese;
    //数学
    private int math;
    //英语
    private int english;

    public StudentText() {
    }

    public StudentText(String name, int age, int chinese, int math, int english) {
        this.name = name;
        this.age = age;
        this.chinese = chinese;
        this.math = math;
        this.english = english;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 获取
     * @return chinese
     */
    public int getChinese() {
        return chinese;
    }

    /**
     * 设置
     * @param chinese
     */
    public void setChinese(int chinese) {
        this.chinese = chinese;
    }

    /**
     * 获取
     * @return math
     */
    public int getMath() {
        return math;
    }

    /**
     * 设置
     * @param math
     */
    public void setMath(int math) {
        this.math = math;
    }

    /**
     * 获取
     * @return english
     */
    public int getEnglish() {
        return english;
    }

    /**
     * 设置
     * @param english
     */
    public void setEnglish(int english) {
        this.english = english;
    }

    public String toString() {
        return this.getName() + "的总分是" + (this.getChinese() + this.getMath() + this.getEnglish()) + "\nStudentText{name = " + name + ", age = " + age + ", chinese = " + chinese + ", math = " + math + ", english = " + english + "}";
    }

    @Override
    public int compareTo(StudentText o) {
        //比较俩者总分
        int i = (this.getChinese() + this.getMath() + this.getEnglish()) - (o.getChinese() + o.getMath() + o.getEnglish());
        //比较语文
        i = i == 0 ? this.getChinese() - o.getChinese() : i;
        //比较数学
        i = i == 0 ? this.getMath() - o.getMath() : i;
        //比较英语(可以省略,因为总分语文数学都一样,英语一定一样)
        i = i == 0 ? this.getEnglish() - o.getEnglish() : i;
        //比较年龄
        i = i == 0 ? this.getAge() - o.getAge() : i;
        //比较名字
        i = i == 0 ? this.getName().compareTo(o.getName()) : i;
        return i;
    }
}

public static void main(String[] args) {
        /*
        需求:创建五个学生对象
        属性:姓名、年龄、语文成绩、数学成绩、英语成绩
        按照总分从高到低输出到控制台
        如果总分一样,按照语文成绩排
        如果语文一样,按照数学成绩排
        如果数学一样,按照英语成绩排
        如果英语一样,按照年龄排
        如果年龄一样,按照姓名的字母顺序排
        如果都一样,认为是同一个学生,不存

        在遍历集合时,看到总分
         */

        //创建学生对象
        StudentText s1 = new StudentText("zhangsan", 23 , 90, 99, 50);
        StudentText s2 = new StudentText("lisi", 24 , 90, 98, 50);
        StudentText s3 = new StudentText("wangwu", 25 , 95, 100, 30);
        StudentText s4 = new StudentText("zhaoliu", 26 , 60, 99, 70);
        StudentText s5 = new StudentText("qianqi", 27 , 70, 80, 70);

        //创建集合对象并添加元素
        TreeSet<StudentText> ts = new TreeSet<>();
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);

        //打印集合
        for (StudentText t : ts) {
            System.out.println(t);
        }
        /*
        结果:
        qianqi的总分是220
        StudentText{name = qianqi, age = 27, chinese = 70, math = 80, english = 70}
        wangwu的总分是225
        StudentText{name = wangwu, age = 25, chinese = 95, math = 100, english = 30}
        zhaoliu的总分是229
        StudentText{name = zhaoliu, age = 26, chinese = 60, math = 99, english = 70}
        lisi的总分是238
        StudentText{name = lisi, age = 24, chinese = 90, math = 98, english = 50}
        zhangsan的总分是239
        StudentText{name = zhangsan, age = 23, chinese = 90, math = 99, english = 50}
         */

    }

总结

特点:

1.可排序、不重复、无索引

2.底层基于红黑树实现排序,增删改查性能好

自定义排序方式:

1.默认排序/自然排序:Javabean类实现Comparable接口指定比较规则

2.比较器排序:创建TreeSet对象时,传递比较器Comparator指定规则

使用原则:默认使用第一种,如果第一种不能满足当前需求,就使用第二种

如果方式一和二同时存在,以方式二为准,因为构造方法中传入了比较器

方法返回值的特点:

负数:表示当前添加的元素是小的,存左边

正数:表示当前添加的元素是大的,存右边

0:表示当前添加的元素已经存在,舍弃

单列集合总结

在以后的实际开发中,一般使用ArrayList和HashSet最多

1.如果想要集合中的元素可重复:

用ArrayList集合,基于数组的(用的最多)

2.如果想对集合中的元素去重,用HashSet集合,基于哈希表的(用的最多)

有些特殊情况:

3.如果想要集合中的元素可重复,而且当前的增删操作明显多于查询

用LinkedList集合,基于链表的

4.如果相对集合中的元素去重,而且保证存取顺序

用LinkedHashSet,基于哈希表和双链表,效率低于HashSet

5.如果想对集合中的元素进行排序

用TreeSet集合,基于红黑树。后续也可以用List集合实现排序

源码分析

set集合底层源码
HashSet底层new HashMap集合
添加对象时候也调用map.put

LinkedHashSet底层会调用super里面的构造
super中new LinkedHashMap

TreeSet底层new TreeMap集合

所以想知道set集合底层,需要先学习Map集合......

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值