1、Set集合
1.1 Set集合概述
java.util.Set
接口和java.util.List
接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。Set集合有多个子类:java.util.HashSet、java.util.LinkedHashSet、java.util.TreeSet
1.2 Set集合的特点
Set集合中的元素不可重复
Set集合没有索引
总结: 不可重复性、无序性
2、HashSet集合
2.1 HashSet概述
HashSet 是 Set 接口的主要实现类,大多数时候使用 Set 集合时都使用这个实现类。
HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存储、查找、删除性能。
HashSet 具有以下特点:
- 不能保证元素的排列顺序
- HashSet 不是线程安全的
- 集合元素可以是 null
HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法得到的哈希值相等,并且两个对象的 equals()方法返回值为true。
对于存放在Set容器中的对象,对应的类一定要重写hashCode()和equals(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
HashSet集合中元素的无序性,不等同于随机性。这里的无序性与元素的添加位置有关。具体来说:我们在添加每一个元素到数组中时,具体的存储位置是由元素的hashCode()调用后返回的hash值决定的。导致在数组中每个元素不是依次紧密存放的,表现出一定的无序性。
2.2 HashSet集合的特点
HashSet集合中的元素不可重复
HashSet集合没有索引
HashSet集合是无序的(存储元素的顺序与取出元素顺序可能不一致)
总结: 不可重复性、无序性
2.3 HashSet常用方法
package com.suyv.set;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 11:46
*@Description: HashSet常用方法
*/
public class HashSetDemo01 {
public static void main(String[] args) {
Set set = new HashSet();
// 添加数据
set.add("张三");
set.add("lisi");
set.add(null);
set.add(new Date());
set.add(10);
// 遍历set
for (Object obj : set) {
System.out.println(obj);
}
// 删除数据
set.remove("lisi");
System.out.println(set); // [null, Sat Dec 16 11:52:29 CST 2023, 张三, 10]
// 获取长度
System.out.println(set.size()); // 4
// 判断非空
System.out.println(set.isEmpty()); // false
// 清空数据
set.clear();
System.out.println(set); // []
}
}
如何保证Hashset集合唯一?
底层依赖 两个方法:hashCode()和equals()。
步骤:
首先比较哈希值
如果相同,继续走,比较地址值或者走equals()
如果不同,就直接添加到集合中
按照方法的步骤来说:
先看hashCode()值是否相同
相同:继续走equals()方法
返回true: 说明元素重复,就不添加
返回false:说明元素不重复,就添加到集合
不同:就直接把元素添加到集合
如果类没有重写这两个方法,默认使用的Object()。一般来说一样。
而String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉。只留下一个。
2.4 HashSet存储自定义类型元素
2.4.1 案例1
定义Student类--不重写hashCode()和equals()
package com.suyv.set;
import java.util.Objects;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 11:55
*@Description: 实体类:Student
*/
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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
定义测试类
package com.suyv.set;
import java.util.HashSet;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 11:59
*@Description: HashSet测试添加自定义实体类对象
*/
public class HashSetDemo02 {
public static void main(String[] args) {
//创建集合对象 该集合中存储 Student类型对象
HashSet<Student> stuSet = new HashSet<Student>();
//存储
stuSet.add(new Student("于谦", 43));
stuSet.add(new Student("于谦", 43));
stuSet.add(new Student("郭麒麟", 23));
stuSet.add(new Student("郭麒麟", 23));
for (Student stu2 : stuSet) {
System.out.println(stu2);
}
}
}
结果:
2.4.2 案例2
定义Student类--重写hashCode()和equals()
package com.suyv.set;
import java.util.Objects;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 11:55
*@Description: 实体类:Student
*/
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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//不需要你手动重写Object hashCode和equals ,再去测试
@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);
}
}
定义测试类
package com.suyv.set;
import java.util.HashSet;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 11:59
*@Description: HashSet测试添加自定义实体类对象
*/
public class HashSetDemo02 {
public static void main(String[] args) {
//创建集合对象 该集合中存储 Student类型对象
HashSet<Student> stuSet = new HashSet<Student>();
//存储
stuSet.add(new Student("于谦", 43));
stuSet.add(new Student("于谦", 43));
stuSet.add(new Student("郭麒麟", 23));
stuSet.add(new Student("郭麒麟", 23));
for (Student stu2 : stuSet) {
System.out.println(stu2);
}
}
}
结果:
2.5 HashSet集合存储数据的结构
JDK的版本不同,HashSet集合的数据结构有所不同:
JDK8之前:数组+链表
JDK8之后:数组+链表+红黑树
以上数据结构我们称之为是哈希表
2.5.1 什么是哈希表
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。
3、LinkedHashSet
3.1 什么是LinkedHashSet
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
在HashSet下面有一个子类java.util.LinkedHashSet,它是 链表 和 哈希表 组合的一个数据存储结构。
3.2 LinkedHashSet集合的特点
LinkedHashSet 是 HashSet 的子类,不允许集合元素重复。
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以添加顺序保存的。
LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
3.3 LinkedHashSet常用方法
package com.suyv.set;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 12:11
*@Description: LinkedHashSet常用方法
*/
public class LinkedHashSetDemo01 {
public static void main(String[] args) {
Set set = new LinkedHashSet();
// 添加数据
set.add("张三");
set.add("lisi");
set.add(null);
set.add(new Date());
set.add(10);
// 遍历set
for (Object obj : set) {
System.out.println(obj);
}
// 删除数据
set.remove("lisi");
System.out.println(set); // [张三, null, Sat Dec 16 12:21:11 CST 2023, 10]
// 获取长度
System.out.println(set.size()); // 4
// 判断非空
System.out.println(set.isEmpty()); // false
// 清空数据
set.clear();
System.out.println(set); // []
}
}
4、TreeSet
使用元素的自然排序对元素进行排序或者根据创建set时提供的Comparable排序(定制排序)具体取决于你用的构造方法
4.1 TreeSet概述
TreeSet 是 SortedSet 接口的实现类,TreeSet 可以按照添加的元素的指定的属性的大小顺序进行遍历。
TreeSet底层使用红黑树结构存储数据
新增的方法如下: (了解)
- Comparator comparator()
- Object first()
- Object last()
- Object lower(Object e)
- Object higher(Object e)
- SortedSet subSet(fromElement, toElement)
- SortedSet headSet(toElement)
- SortedSet tailSet(fromElement)
TreeSet特点:不允许重复、实现排序(自然排序或定制排序)
TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。
- 自然排序:TreeSet 会调用集合元素的
compareTo(Object obj)
方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
-
- 如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。
- 实现
Comparable
的类必须实现compareTo(Object obj)
方法,两个对象即通过compareTo(Object obj)
方法的返回值来比较大小。
- 定制排序:如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过
Comparator
接口来实现。需要重写compare(T o1,T o2)
方法。
-
- 利用
int compare(T o1,T o2)
方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。 - 要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。
- 利用
因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象。
对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 或compare(Object o1,Object o2)方法比较返回值。返回值为0,则认为两个对象相等。
4.1 TreeSet自然排序
4.1.1 String类的自然排序
package com.suyv.set;
import org.junit.Test;
import java.util.Set;
import java.util.TreeSet;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 12:30
*@Description: TreeSet自然排序--String
*/
public class TreeSetDemo01 {
@Test
public void Test01(){
Set set = new TreeSet();
set.add("AA");
set.add("BB");
set.add("CC");
set.add("DD");
//set.add(123); //报ClassCastException的异常
System.out.println(set); // [AA, BB, CC, DD]
}
}
4.1.2 自定义类的自然排序
案例1
自定义实体类:User
package com.suyv.set;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 13:54
*@Description: 实体类User
*/
public class User implements Comparable{
private String name;
private int age;
public User() {
}
public User(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 "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 比如:按照年龄从小到大排序
@Override
public int compareTo(Object o) {
if(this == o){
return 0;
}
if(o instanceof User){
User u = (User)o;
return this.age - u.age;
}
throw new RuntimeException("类型不匹配");
}
}
TreeSet测试类
package com.suyv.set;
import java.util.Iterator;
import java.util.TreeSet;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 13:57
*@Description: TreeSet自然排序的使用
*/
public class TreeSetDemo02 {
public static void main(String[] args) {
TreeSet set = new TreeSet();
set.add(new User("Tom",12));
set.add(new User("Rose",23));
set.add(new User("Jerry",2));
set.add(new User("Eric",18));
set.add(new User("Tommy",44));
set.add(new User("Jim",23));
set.add(new User("Maria",18));
// set.add("Tom");
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println(set.contains(new User("Jack", 23))); // true
}
}
案例2
自定义实体类:User
package com.suyv.set;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 13:54
*@Description: 实体类User
*/
public class User implements Comparable{
private String name;
private int age;
public User() {
}
public User(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 "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 比如:先比较年龄从小到大排列,如果年龄相同,则继续比较姓名,从大到小
@Override
public int compareTo(Object o) {
if(this == o){
return 0;
}
if(o instanceof User){
User u = (User)o;
int value = this.age - u.age;
if(value != 0){
return value;
}
return -this.name.compareTo(u.name);
}
throw new RuntimeException("类型不匹配");
}
}
TreeSet测试类
package com.suyv.set;
import java.util.Iterator;
import java.util.TreeSet;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 13:57
*@Description: TreeSet自然排序的使用
*/
public class TreeSetDemo02 {
public static void main(String[] args) {
TreeSet set = new TreeSet();
set.add(new User("Tom",12));
set.add(new User("Rose",23));
set.add(new User("Jerry",2));
set.add(new User("Eric",18));
set.add(new User("Tommy",44));
set.add(new User("Jim",23));
set.add(new User("Maria",18));
// set.add("Tom");
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println(set.contains(new User("Jack", 23))); // false
}
}
4.2 TreeSet定制排序
Comparator 可以看成一个外部比较器,好处不用修改原代码直接实现
package com.suyv.set;
import org.junit.Test;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 14:55
*@Description: TreeSet定制排序
*/
public class TreeSetDemo03 {
@Test
public void Test01(){
Comparator comparator = new Comparator() {
/*
* 按照姓名从小到大排列,如果姓名相同,继续比较age,按照从大到小排列
* */
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
int value = u1.getName().compareTo(u2.getName());
if(value != 0){
return value;
}
return -(u1.getAge() - u2.getAge());
}
throw new RuntimeException("类型不匹配");
}
};
TreeSet set = new TreeSet(comparator);
User u1 = new User("Tom",23);
User u2 = new User("Jerry",43);
User u3 = new User("Rose",13);
User u4 = new User("Jack",23);
User u5 = new User("Tony",33);
User u6 = new User("Tom",33);
set.add(u1);
set.add(u2);
set.add(u3);
set.add(u4);
set.add(u5);
set.add(u6);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
Comparable<T> 内部比较器,需要修改原代码,不符合OCP原则
重写方法: public int compareTo(T t)
Comparator 可以看成一个外部比较器,好处不用修改原代码直接实现
重写方法: public int compare(Ojbect s1, Ojbect s2)
返回值类型:int 等于0 表示相等 大于0表示升序 小于0表示是降序