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