目录
1 Set 集合
1.1 Set 集合概述
Set
集合
- 不可以存储重复元素(可以去除重复)
- 存取顺序不一致
- 没有索引,不能使用普通 for 循环遍历,也不能通过索引来获取、删除 Set 集合里面的元素
Set
系列集合:添加的元素无序、不可重复、无索引
HashSet
:添加的元素无序、不可重复、无索引;LinkedHashSet
: 添加的元素 有序、不可重复、无索引TreeSet
: 按照大小默认升序排序、不可重复、无索引
1.2 Set 集合的使用
Set
集合的 API 就是直接继承 Collection
的API
存储字符串并遍历
public static void main(String[] args) {
//Set<String> set = new HashSet<>();
//Set<String> set = new LinkedHashSet<>();
Set<String> set = new TreeSet<>();
set.add("ccc");
set.add("aaa");
set.add("aaa");
set.add("bbb");
System.out.println(set); // [aaa, bbb, ccc]
Iterator<String> it = set.iterator();
while(it.hasNext()){
System.out.print(it.next()+" "); // aaa bbb ccc
}
for (String s : set) {
System.out.print(s+"*"); // aaa*bbb*ccc*
}
}
2 TreeSet 类
2.1 TreeSet 类概述
TreeSet 类
- 底层是红黑树,必须给定排序规则,将元素按照规则进行排序
TreeSet()
:根据其元素的自然排序进行排序- 数值类型的数据,默认按照大小升序排序
- 对于字符串类型,默认按照首字符的编号升序排序
TreeSet(Comparator comparator)
:根据指定的比较器进行排序- 对于自定义类型的对象,无法直接排序 (必须制定排序规则)
- 不可以存储重复元素
- 没有索引
2.2 TreeSet 基本使用
1️⃣ 存储 Integer
类型的整数并遍历
public static void main(String[] args) {
TreeSet<Integer> ts = new TreeSet<>();
ts.add(5);
ts.add(3);
ts.add(4);
ts.add(1);
ts.add(2);
System.out.println(ts); // [1, 2, 3, 4, 5]
}
2️⃣ 存储学生对象并遍历
已存在标准的学生类
测试类如下:
import java.util.TreeSet;
public class Demo {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("zhangsan",28);
Student s2 = new Student("lisi",27);
Student s3 = new Student("wangwu",29);
Student s4 = new Student("zhaoliu",28);
Student s5 = new Student("qianqi",30);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
System.out.println(ts);
}
}
运行测试类会报错,因为不知道按照什么规则(身高、年龄……)对学生对象排序。由此可得,想要使用TreeSet,需要指定排序规则。
2.3 TreeSet 排序
2.3.1 自然排序
自然排序:即让自定义类实现 Comparable
接口重写里面的 compareTo
方法来制定比较规则。
🙋举个栗子:
案例需求:
- 存储学生对象并遍历,创建
TreeSet
集合使用无参构造方法 - 要求:按照年龄从小到大排序,如果年龄一样,则按照姓名首字母排序;如果姓名和年龄都一样,才认为是同一个学生对象,不存入。
实现步骤:
- 使用空参构造创建
TreeSet
集合- 用
TreeSet
集合存储自定义对象,无参构造方法 使用的是自然排序对元素进行排序的
- 用
- 自定义的
Student
类实现 Comparable接口- 自然排序,就是让元素所属的类实现
Comparable
接口,重写 compareTo(T o)方法
- 自然排序,就是让元素所属的类实现
- 重写接口中的
compareTo
方法- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
示例代码:
学生类
public class Student implements Comparable<Student>{ // Comparable接口有泛型且要跟集合的数据类型保持一致
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
/*
// 只按照年龄升序排序
@Override
public int compareTo(Student o) {
// return this.age - o.age; // 去除了年龄有重复的学生数据
return this.age - o.age >= 0 ? 1 : -1; // 没有去除年龄有重复的学生数据
}*/
@Override
public int compareTo(Student o) {
//按照对象的年龄进行排序
//主要判断条件(年龄)
int result = this.age - o.age; // this表示当前正在存的元素,o表示已存的元素
//次要判断条件(名字的字母序)
result = result == 0 ? this.name.compareTo(o.getName()) : result;
return result;
}
}
测试类
import java.util.TreeSet;
public class Demo {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("zhangsan",28);
Student s2 = new Student("lisi",27);
Student s3 = new Student("wangwu",29);
Student s4 = new Student("zhaoliu",28);
Student s5 = new Student("qianqi",30);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
System.out.println(ts);
// 年龄28有重复,去除s4的数据
// [Student{name='lisi', age=27}, Student{name='zhangsan', age=28}, Student{name='wangwu', age=29}, Student{name='qianqi', age=30}]
// 按照年龄升序排序的同时,不想不想去除年龄重复的学生数据
// [Student{name='lisi', age=27}, Student{name='zhangsan', age=28}, Student{name='zhaoliu', age=28}, Student{name='wangwu', age=29}, Student{name='qianqi', age=30}]
}
}
❗️注意:以下两个compareTo方法是不一样的
- 第二个compareTo方法是字符串的方法
public static void main(String[] args) {
String s1 = "aaa";
String s2 = "ab";
System.out.println(s1.compareTo(s2)); // -1
//首先比较第一个字母,如果第一个字母是一样的,那么继续比较后面的字母
//当不一样的时候,就拿着对应的码表值97,减去 b的码表值 98
//认为a是比b要小的。
}
2.3.2 比较器排序
比较器排序:即TreeSet
集合有参数构造器,可以设置Comparator
接口对应的比较器对象,来制定比较规则。
🙋举个栗子:
案例需求:
- 存储老师对象并遍历,创建TreeSet集合使用带参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
实现步骤:
- 用TreeSet集合存储自定义对象,带参构造方法 使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
示例代码:
老师类
public class Teacher {
private String name;
private int age;
public Teacher() {
}
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
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 "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类
public static void main(String[] args) {
TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
@Override
public int compare(Teacher o1, Teacher o2) {
//o1表示现在要存入的那个元素
//o2表示已经存入到集合中的元素
//主要条件
int result = o1.getAge() - o2.getAge(); // 升序
//int result = o2.getAge() - o1.getAge(); // 降序
//次要条件
result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
return result;
}
});
Teacher t1 = new Teacher("zhangsan",23);
Teacher t2 = new Teacher("lisi",22);
Teacher t3 = new Teacher("wangwu",24);
Teacher t4 = new Teacher("zhaoliu",24);
ts.add(t1);
ts.add(t2);
ts.add(t3);
ts.add(t4);
System.out.println(ts);
}
❗️注意:
- 对于浮点数的排序比较,例如按照身高升序排序:
return Double.compare(o1.getHeight-o2.getHeight);
2.3.3 两种排序方式总结
🌈 两种排序方式总结:
- 自然排序:自定义类实现
Comparable
接口,重写compareTo
方法,根据返回值进行排序 - 比较器排序:创建
TreeSet
对象的时候传递Comparator
的实现类对象,重写compare
方法,根据返回值进行排序
❗️注意:
- 如果
TreeSet
集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器
🎠 两种方式中关于返回值的规则:
- 如果返回值为负数,表示当前存入的元素是较小值,存左边
- 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
- 如果返回值为正数,表示当前存入的元素是较大值,存右边
案例:按照字符串长短排序
要求:存入4个字符串“c”、“ab”、“df”、“qwer”,按照长度排序,如果一样长则按照首字母排序。
请自行选择比较器排序和自然排序两种方式。
示例代码:
public static void main(String[] args) {
// TreeSet<String> ts = new TreeSet<>();
// ts.add("c");
// ts.add("ab");
// ts.add("df");
// ts.add("qwer");
// System.out.println(ts); // [ab, c, df, qwer]
/*
* 通过查看String源码可知,String类已经实现了Comparable接口,
* 即String类的自然排序规则Java内部已经帮我们写好了,且默认规则为按照字母字典序排列
* 所以我们应该选择比较器排序
* */
// 1. 匿名内部类方式
// TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
// @Override
// public int compare(String o1, String o2) {
// int result = o1.length() - o2.length();
// result = result==0?o1.compareTo(o2):result;
// return result;
// }
// });
// 2. lambda函数方式
TreeSet<String> ts = new TreeSet<>(
(String o1, String o2)->{
int result = o1.length() - o2.length();
result = result == 0 ? o1.compareTo(o2) : result;
return result;
}
);
ts.add("c");
ts.add("ab");
ts.add("df");
ts.add("qwer");
System.out.println(ts); // [ab, c, df, qwer]
}
3 HashSet 类
3.1 HashSet 类概述
HashSet类
- 底层数据结构是 哈希表
- 不能保证存储和取出的顺序完全一致
- 是 Set 集合,元素唯一,不可以存储重复元素
- 没有索引,不能使用普通 for 循环遍历
3.2 HashSet 基本使用
存储字符串并遍历
public static void main(String[] args) {
HashSet<String> hs = new HashSet<>();
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("java");
hs.add("java");
hs.add("java");
hs.add("java");
hs.add("java");
Iterator<String> it = hs.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("=============================");
for (String s : hs) {
System.out.println(s);
}
}
3.3 哈希值
哈希值(哈希码值):
- 是JDK根据对象的 地址 或者 属性值,算出来的
int
类型的整数
哈希值的特点:
- 如果没有重写
hashCode()
方法,那么是根据对象的地址值计算出的哈希值。同一个对象多次调用hashCode()
方法返回的哈希值是相同的,不同对象的哈希值是不同的 - 而重写
hashCode()
方法,一般都是通过对象的属性值计算出哈希值。如果不同的对象属性值是一样的,那么计算出的哈希值也是一样的Alt
+Insert
选择equals() and hashCode()
,默认选项直接next
,自动生成重写方法
获取哈希值:
- Object类中的
public int hashCode()
:返回对象的哈希码值
示例代码:
学生类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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 boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
//我们可以对Object类中的hashCode方法进行重写
//在重写之后,就一般是根据对象的属性值来计算哈希值的。
//此时跟对象的地址值就没有任何关系了。
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类
import java.util.HashSet;
import java.util.Set;
public class Demo {
public static void main(String[] args) {
Set<Student> students=new HashSet<>();
Student s1 = new Student("xiaozhi",23);
Student s2 = new Student("xiaomei",22);
// 1. 同一个对象多次调用hashCode()时其返回值一样
// 因为在Object类中,是根据对象的地址值计算出来的哈希值。
System.out.println(s1.hashCode());// 265724891
System.out.println(s1.hashCode());// 265724891
// 2. 不同对象的哈希值是不一样的
System.out.println(s2.hashCode());// 265334724
Student s3 = new Student("LALA",23);
Student s4 = new Student("LALA",23);
System.out.println(s3.hashCode()); // 72199085
System.out.println(s4.hashCode()); // 72199085
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
System.out.println(students);
// [Student{name='LALA', age=23}, Student{name='xiaomei', age=22}, Student{name='xiaozhi', age=23}]
}
}
4 LinkedHashSet 类
LinkedHashSet 类中的元素有序、不重复、无索引
- 这里的有序指的是保证存储和取出的元素顺序是一致的
LinkedHashSet
类原理:其底层数据结构依然是哈希表,只是每个元素有额外的多了一个 双链表 机制记录存储的顺序