1、Set系列集合

1.1、Set集合的实现类:

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

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

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

1.2、Set系列集合的遍历方式:可以使用三种遍历集合的方式,就是不能使用索引进行遍历。

2、HashSet

1、HashSet集合底层采用哈希表存储数据,哈希表是一种对于增删查改性能都较好的结构

2、哈希表的组成:

jdk8之前:数组 + 链表

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

3、哈希值(对象的整数表现形式):

①、是根据hashCode方法计算出来的int类型的整数

②、该方法定义在Object类中,所有的对象都可以调用,默认使用地址值进行计算

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

4、哈希值的特点:

①、如果没有重写hashCode方法,不同的对象计算出来的哈希值是不同的

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

③、在小部分情况下,不同的属性值或者不同的地址计算出来的哈希值也有可能一样(俗称哈希碰撞),但是发生这种概率的情况是比较小的。

如下所示:

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;
    }

    public String toString() {
        return "Student{name = " + name + ", 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);
    }

    //重写hashCode方法
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

5、HashSet存储数据的底层原理

①、通过hashCode方法计算出哈希值

②、通过哈希值和数组长度计算出在数组中存储的索引

③、通过索引找到在数组中的位置,并判断在该索引处是否有元素,如果没有,直接存入,如果有,就调用对象的equals方法进行比较,如果两个元素不相同,那么就将新元素存入数组,形成链表

④、在jdk8之前:新元素存入数组,老元素挂在新元素的下面

jdk8之后:新元素直接挂在老元素的下面

==注意:使用HashSet存储自定义数据类型时,一定要重写hashCode和equals方法,不然会有问题!!==

3、LinkedHashSet

1、底层原理:底层数据结构依然是哈希表,只是每个元素又额外的多了一个双链表的机制来记录存储的顺序。

4、LinkedHashSet和HashSet的选择

如果只是去重,使用HashSet就行,如果要求既要去重又要保证存取和读取相同,那么就使用LinkedHashSet。

5、TreeSet

1、特点:

①、不重复、无索引、可排序

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

实例代码如下所示:

import java.util.TreeSet;

public class Test {
    public static void main(String[] args) {
        TreeSet<Integer> st = new TreeSet<>();
        st.add(12);
        st.add(45);
        st.add(2);
        st.add(22);
        st.add(21);

        //遍历输出
        for (Integer integer : st) {
            System.out.println(integer);
        }

    }

}

输出结果(默认从小到大):

2 12 21 22 45

2、如果要比较自定义数据类型,有两种方式:

方式一:实现Comparable接口,如下所示:

import java.util.Objects;

public class Student implements Comparable<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 + "}";
    }


    @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);
    }

    //重写hashCode方法
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    //重写接口里面的方法
    @Override
    public int compareTo(Student o) {
        return Integer.compare(this.age,o.getAge());
    }
}

//测试类
import java.util.TreeSet;

public class Test {
    public static void main(String[] args) {
        TreeSet<Student> st = new TreeSet<>();
        st.add(new Student("qz1027",11));
        st.add(new Student("qz1027",12));
        st.add(new Student("qz1027",2));
        st.add(new Student("qz1027",8));

        //遍历输出
        for (Student student : st) {
            System.out.println(student.toString());
        }

    }

}

输出结果:

Student{name = qz1027, age = 2} Student{name = qz1027, age = 8} Student{name = qz1027, age = 11} Student{name = qz1027, age = 12}

方式二:在创建TreeSet对象时传递Comparator实现对象,如下所示:

 TreeSet<Student> st1 = new TreeSet<>(new Comparator<Student>() {	//这里可以使用Lambda
            @Override
            public int compare(Student o1, Student o2) {
                return -Integer.compare(o1.getAge(),o2.getAge());
            }
        });