Set集合是Collection集合下的一个分支。
特点:无序;添加数据的顺序和取出的顺序不一致;不重复;无索引;
Set集合具有三个常见的实现类:
1、HashSet:无序、不重复、无索引
2、LinkedHashSet:有序、不重复、无索引
3、TreeSet:排序、不重复、无索引
示例
package HashSetDemo;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
public class SetDemo1 {
//目标:认识Set家族集合的特点
//Set的常用方法基本就是Collection提供的,几乎没有新增的常用方法
public static void main(String[] args) {
//1、创建一个Set集合
//HashSet无序、不重复、无索引
//Set<String> set = new HashSet<>();
//LinkedHashSet有序、不重复、无索引
Set<String> set = new LinkedHashSet<>();
set.add("大数据");
set.add("java");
set.add("java");
set.add("c++");
set.add("python");
set.add("java");
set.add("鸿蒙");
System.out.println(set);
//2、创建一个TreeSet集合,排序(默认按升序排序),不重复,无索引
Set<Double> set1 = new TreeSet<>();
set1.add(3.14);
set1.add(2.14);
set1.add(1.14);
set1.add(4.14);
set1.add(3.14);
System.out.println(set1);
System.out.println("===========================");
//哈希值是一个随机返回的值
//用hashCode方法获取对象的哈希值
//hashCode方法返回值是一个int类型的值,所以可以作为int类型的数组下标
//同一个对象的哈希值是相同的
String s1="java";
String s2="python";
System.out.println(s1.hashCode());
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s2.hashCode());
}
}
HashSet集合根据哈希值来判断两个数据是否相同。哈希值是一个int类型的随机值,Java中每个数据都有其对应的哈希值,Java中的所有对象,都可以用Object类提供的hashCode方法,返回该对象的哈希值。
对象哈希值的特点
同一个对象多次调用hashCode()方法返回的哈希值是相同的。不同的对象,他们的哈希值大概率不相等,但也有可能相等(哈希碰撞)。
HashSet集合的底层原理
基于哈希表实现
1、创建一个长度为16的默认长度的数组,默认加载因子为0.75,数组名table。
2、用元素的哈希值对数组的长度做运算计算出应存入的位置。
3、判断当前位置是否是null,如果是null直接存入
4、如果不为null,用equals方法判断是否相同,如果相同不存入,不相同则存入数组。
JDK8以前的哈希表:数组加链表,新元素若是与老元素计算到了一个位置,新元素占老元素位置,老元素挂新元素下。JDK8以后,新元素直接挂老元素下。
当总长度达到当前长度乘加载因子的数量时,开始扩展数组,直接翻一倍。
JDK8开始,链表长度大于8(一个元素下挂着8个以上元素),且数组长度大于等于64,自动将链表转换为红黑树(左边比根节点小,右边比根节点大)。
当存入的类型为自定义对象时,因为类对象虽然内容相同,但本质上是不同的对象,因此会导致其哈希值不同,重复存入HashSet数组,此时需要在类内重写equals方法和hashCode方法进行去重(或使用@Data,其不仅有getset方法,还重写了equals和hashCode等方法)。
package HashSetDemo;
import java.util.Objects;
public class Student {
private String name;
private int age;
private String address;
private String phone;
public Student() {
}
public Student(int age, String address, String name, String phone) {
this.age = age;
this.address = address;
this.name = name;
this.phone = phone;
}
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;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
//重写HashCode方法和equals方法用于去重
//两个对象内容一样结果为true
@Override
public boolean equals(Object o) {
if (this == o) return true;//自己和自己比,直接true
//如果o 为空 或者 o 的类型和当前对象不一样,返回false
if (o == null || this.getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name) && Objects.equals(address, student.address) && Objects.equals(phone, student.phone);
}
@Override
public int hashCode() {
return Objects.hash(name, age, address, phone);
}
@Override
public String toString() {
return "name:"+name+" "+"age:"+age+" "+"address:"+address+" "+"phone:"+phone;
}
}
package HashSetDemo;
import java.util.HashSet;
import java.util.Set;
public class SetDemo2 {
public static void main(String[] args) {
//掌握HashSet的去重操作
Student s1 = new Student(18,"北京","张三","123456");
Student s2 = new Student(18,"北京","李四","989893");
Student s3 = new Student(18,"北京","张三","123456");
Student s4 = new Student(18,"北京","李四","989893");
//无法判断是否重复,因为虽然对象内的成员变量相同,但是返回的哈希值不同,所以无法去重
//通过重写结构体内的hashcode和equals方法,使得两个对象返回的hashcode值相同,从而实现去重
Set<Student> set = new HashSet<>();
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
System.out.println(set);
}
}
TreeSet对自定义对象的排序
1、对象类实现一个Comparable接口,重写compare方法,指定大小比较规则
2、public TreeSet(Comparator c)集合自带比较器Comparator对象,指定比较规则
package HashSetDemo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Teacher implements Comparable<Teacher>{
private String name;
private int age;
private double salary;
@Override
public String toString()
{
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}'+"\n";
}
//t2.compareTo(t1)
//t2==this 比较者
//t1==o 被比较者
//规定:如果t2大于t1,返回正数;如果t2小于t1,返回负数;如果t2等于t1,返回0
@Override
public int compareTo(Teacher o) {
//按照年龄升序排序
// if (this.age>o.age)
// return 1;
// else if (this.age<o.age)
// return -1;
// return 0;
// }
return this.age-o.age;//升序
//return o.age-this.age;//降序
}
}
package HashSetDemo;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class SetDemo3 {
public static void main(String[] args) {
//TreeSet对于自定义对象的排序
Set<Teacher> teachers= new TreeSet<>(); //排序、去重、无索引
//没有默认的排序规则,所以需要重写compareTo方法
teachers.add(new Teacher("老陈", 30, 5000));
teachers.add(new Teacher("小王", 27, 6300));
teachers.add(new Teacher("小李", 29, 5200));
teachers.add(new Teacher("小张", 27, 3200));
//解决方案
//1、对象类实现一个Comparable接口,重写compare方法,指定大小比较规则
//2、public TreeSet(Comparator c)集合自带比较器Comparator对象,指定比较规则
System.out.println(teachers);
//优先使用匿名内部类里的规则
Set<Teacher> teachers1 = new TreeSet<>(new Comparator<Teacher>() {
// @Override
// public int compare(Teacher o1, Teacher o2) {
// return o2.getAge()-o1.getAge();//年龄降序
// }
//薪水升序
@Override
public int compare(Teacher o1, Teacher o2) {
return Double.compare(o1.getSalary(),o2.getSalary());
}
});
teachers1.add(new Teacher("老陈", 30, 5000));
teachers1.add(new Teacher("小王", 28, 630));
teachers1.add(new Teacher("小李", 29, 5200));
teachers1.add(new Teacher("小张", 27, 3200));
System.out.println(teachers1);
}
}
如果希望记住元素的添加顺序,存储重复的元素,需要频繁的查找数据,使用ArrayList集合(基于数组)。
如果希望记住元素的添加顺序,且首尾增删情况较多,应使用LinkedList集合(基于双链表)。
不在意元素顺序,没有元素需要重复存储,只希望增删改查能快,用HashSet集合(基于哈希表)。
希望记住元素的添加顺序,没有重复元素需要存储,且希望增删改查都快,用LinkedHashSet集合(基于哈希表和双链表)。
要对元素进行排序,没有重复元素需要存储,希望增删改查都快,使用TreeSet集合(基于红黑树)。
以上均为理想情况,应用时需要具体情况具体分析。