本篇章中有些地方没有描述清楚,具体详细情况参考https://blog.csdn.net/b15735105314/article/details/117233527,因为set是基础map实现的。
一、Set概念
Set容器是一个不包含重复元素的Collection,并且最多包含一个null元素,它和List容器相反,Set容器不能保证其元素的顺序。
最常用的两个Set接口的实现类是HashSet和TreeSet.
二、HashSet
HashSet扩展AbstractSet并且实现Set接口
HashSet使用散列表(又称哈希表)进行存储
构造方法:
HashSet() | 默认构造方法 |
HashSet(Collection c) | 使用collection集合进行初始化 |
HashSet(int capacity) | 指定初始数组的大小 |
HashSet(int capacity, float fillRatio) | 指定初始数组的大小和扩容因子 |
HashSet没有定义任何超过它的父类和接口提供的其他方法
散列集合没有确保其元素的顺序,因为散列处理通常不参与排序,而且每次有新的元素加入的时候,顺序也会发生改变。
散列集合不保证它的元素的顺序。元素加入散列集合的顺序并不一定是他们被迭代读出的顺序。
自定义类作为HashSet的元素的时候,规定要重写类的hashCode() 和 equals() 两个方法。
HashSet通过元素的hashCode() 和 equals() 两个方法判断元素是否相同,如果元素的hashCode()方法相同且equals()也相等,那么HashMap就认为元素是相等的。
总结:HashSet的内部操作的底层数据是HashMap,只是我们操作的是HashMap的Key。
@Getter
@AllArgsConstructor
static class Student implements Comparable<Student>{
private String name;
private Integer age;
@Override
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 Objects.equals(name, student.name) &&
Objects.equals(age, student.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public int compareTo(Student o) {
return this.getName().compareTo(o.getName());
}
}
示例1:
@Test
public void hashSet(){
Set<Student> set = new HashSet<>();
Student stu1 = new Student("唐三", 25);
Student stu2 = new Student("比比东", 50);
Student stu3 = new Student("千道流", 130);
Student stu4 = new Student("波塞西", 200);
Student stu5 = new Student("唐三", 25);
set.add(stu1);
set.add(stu2);
set.add(stu3);
set.add(stu4);
System.out.println(set.add(stu5));
System.out.println(set);
}
输出内容:
false
[Student{name='比比东', age=50}, Student{name='千道流', age=130}, Student{name='唐三', age=25}, Student{name='波塞西', age=200}]
上面实例中,姓名为“唐三”的student在第二次加入的时候是失败的,所以add方法返回false,HashMap在判定key相等的时候,会进行值的替换,HashSet在判定Key相等的时候,后面的值会被忽略掉。
三、TreeSet
TreeSet为使用树来进行存储的Set接口提供了一个工具,对象安升序存储,访问和检索很快
在存储了大量的需要进行快速检索的排序信息的情况下,TreeSet是一个很好的选择。
构造方法:
TreeSet() | 默认构造方法 |
TreeSet(Collection c) | 使用一个Collection对象进行初始化 |
TreeSet(Comparator comp) | 构造TreeSet,并传入比较对象Comparator |
TreeSet(SortedSet ss) |
TreeSet使用Comparator的compare方法(或者元素所属类型实现Comparable接口的compareTo方法)判断元素是否相等,元素是否相等与hashCode() 和 equals() 两个方法没有任何关系,如果compare()或者compareTo()方法返回0就表示元素是相等的。
在创建TreeSet的时候,要么传入Comparator的实现类,要么元素的所属类实现Comparable的compareTo方法,二者必须选择一个;且如果二者同时满足,会选择传入的Comparator的实现类进行元素的比较。
Comparator#compare:
Comparable#compareTo:
总结:TreeSet的内部操作的底层数据是TreeMap,只是我们操作的是TreeMap的key。
示例2:
@Test
public void treeSet(){
Set<Student> set = new TreeSet<>();
Student stu1 = new Student("tangsan", 25);
Student stu2 = new Student("bibidong", 50);
Student stu3 = new Student("qiandaoliu", 130);
Student stu4 = new Student("bosaixi", 200);
Student stu5 = new Student("tangsan", 26);
set.add(stu1);
set.add(stu2);
set.add(stu3);
set.add(stu4);
System.out.println(set.add(stu5));
System.out.println(set);
}
输出内容:
false
[Student{name='bibidong', age=50}, Student{name='bosaixi', age=200}, Student{name='qiandaoliu', age=130}, Student{name='tangsan', age=25}]
Student实现comparable接口的compareTo时,使用的时用户姓名座对比,所以两个“tangsan”student,只有第一次的被存储。第二次存储“tangsan”时返回false。
示例3:
@Test
public void treeSet(){
Set<Student> set = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
if(s1.getName().compareTo(s2.getName()) > 0){
return 1;
} else if (s1.getName().compareTo(s2.getName()) < 0){
return -1;
} else {
return s1.getAge().compareTo(s2.getAge());
}
}
});
Student stu1 = new Student("tangsan", 25);
Student stu2 = new Student("bibidong", 50);
Student stu3 = new Student("qiandaoliu", 130);
Student stu4 = new Student("bosaixi", 200);
Student stu5 = new Student("tangsan", 26);
set.add(stu1);
set.add(stu2);
set.add(stu3);
set.add(stu4);
System.out.println(set.add(stu5));
System.out.println(set);
}
输出内容:
[Student{name='bibidong', age=50}, Student{name='bosaixi', age=200}, Student{name='qiandaoliu', age=130}, Student{name='tangsan', age=25}, Student{name='tangsan', age=26}]
上面示例中传入了Comparator对象,如果传入Comparator对象,那么就不会再使用自然顺序进行排序,Comparator在实现的时候,先使用姓名进行对比,在姓名相同的时候,使用年龄进行对比。所以上面的示例中两个“tangsan”都成功存储,而且25岁的在前,26岁的在后。
四、LinkedHashSet
LinkedHashSet是基于HashSet实现的,所以特性参考HashSet就行,唯一的区别就是LinkedHashSet可以保证输入顺序和输出顺序保持一致。
示例:
@Test
public void linkedHashSet(){
Set<Student> set = new LinkedHashSet<>();
Student stu1 = new Student("唐三", 25);
Student stu2 = new Student("比比东", 50);
Student stu3 = new Student("千道流", 130);
Student stu4 = new Student("波塞西", 200);
Student stu5 = new Student("唐三", 25);
set.add(stu1);
set.add(stu2);
set.add(stu3);
set.add(stu4);
System.out.println(set.add(stu5));
System.out.println(set);
}
输出内容:
false
[Student{name='唐三', age=25}, Student{name='比比东', age=50}, Student{name='千道流', age=130}, Student{name='波塞西', age=200}]