一、set集合概述
Java集合框架中的Set
接口是Collection
接口的一个子接口,它表示一个无序且不包含重复元素的集合。Set
接口的主要目的是确保集合中的元素唯一性,即集合中不允许出现重复的元素。
它继承自Collection
接口,并且有两个主要的实现类:HashSet
和TreeSet
。
二、set的遍历方式
1,使用迭代器
2、用增强for循环
3、不能使用索引方法获取
演示示例:
public class SetDemo {
public static void main(String[] args) {
HashSet set = new HashSet();//添加元素的顺序和输出顺序无关,
set.add("ad");
set.add("ba");
set.add("cv");
set.add("ad");//无法添加重复元素
set.add(new Student("zhangsan",12,98,"1"));
set.add(new Student("zhangsan",12,98,"1"));
System.out.println(set);
System.out.println(set.size());
//不能靠索引遍历,只能用增强for循环和Iterator迭代器
for (Object o:set){
System.out.println(o);
}
System.out.println("");
Iterator iterator=set.iterator();//迭代器
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
三、HashSet
1,HashSet简述
HashSet是Set
接口最常用的实现类之一。它使用哈希表来存储元素,提供快速的添加、删除和查找操作。HashSet
不保证元素的顺序,并且允许包含一个null
元素。
2,主要特点
- 无序性:
HashSet
不保证元素的顺序,即元素的插入和迭代顺序可能不一致。 - 性能:它通常提供更快的添加、删除和查找操作。
- 实现:基于哈希表实现,通过元素的
hashCode()
方法来确定元素存储的位置。
3,代码示例
import java.util.HashSet;//导入HashSet, 用于实现Set接口
public class SetDemo3 {
public static void main(String[] args) {
HashSet set = new HashSet();// 创建HashSet对象
//说明
//1. 在执行add方法后,会返回一个boolean值
//2. 如果添加成功,返回 true, 否则返回false
//3. 可以通过 remove 指定删除哪个对象
System.out.println(set.add("john"));//true
System.out.println(set.add("lucy"));// true
System.out.println(set.add("john"));// false
System.out.println(set.add("jack"));// true
System.out.println(set.add("Rose"));// true
set.remove("john");//删除john
System.out.println("set=" + set);//[jack, Rose, lucy], 顺序不一定
//
set = new HashSet();//创建HashSet对象
System.out.println("set=" + set);//[]
//4 Hashset 不能添加相同的元素/数据?
set.add("lucy");//添加成功
set.add("lucy");//加入不了
set.add(new Dog("tom"));//OK
set.add(new Dog("tom"));//Ok
System.out.println("set=" + set);//[Dog{name='tom'}, Dog{name='tom'},
set.add(new String("feis"));//ok
set.add(new String("feis"));//加入不了,原因是String类重写了equals方法
System.out.println("set=" + set);//[Dog{name='tom'}, Dog{name='tom'},
}
}
class Dog { //定义了Dog类
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
四、Set 接口实现类-LinkedHashSet
1) LinkedHashSet 是HashSet 的子类
2)LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个 数组+双向链表
3)LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序,这使 得元素看起来是以插入顺序保存的。
4) LinkedHashset 不允许添重复元素
使用示例:
package Java基础.常用类.集合.set;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
public class linkhashset1 {
public static void main(String[] args) {
Set<Car> set=new LinkedHashSet();
set.add(new Car("1",1));
set.add(new Car("1",1));
set.add(new Car("2",1));
set.add(new Car("3",1));
set.add(new Car("4",1));
for (Car o:set){
o.toString();
}
}
}
class Car{
private String name;
private int pri;
public Car(String name, int pri) {
this.name = name;
this.pri = pri;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return pri == car.pri && Objects.equals(name, car.name);
}
@Override
public int hashCode() {
return Objects.hash(name, pri);
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", pri=" + pri +
'}';
}
}
五、Set 接口实现类-TreeSet
1 ,TreeSet实现原理
TreeSet使用红黑树结构对加入的元素进行排序存放,所以放入TreeSet中元素必须是可“排序”的。
2,TreeSet主要特点
- 有序性:
TreeSet
保证元素处于排序状态,可以按照自然顺序或通过实现Comparator
接口来指定元素的排序方式。 - 性能:对于需要有序元素集的场景,
TreeSet
是一个好的选择,但通常比HashSet
慢,因为它基于红黑树实现。 - 实现:基于平衡二叉搜索树(通常是红黑树)实现。
3,可排序的对象
TreeSet可是采用两种方法实现排序:自然排序和定制排序。默认情况,TreeSet采用自然排序。
TreeSet调用调用集合元素的CompareTo()方法,根据该方法的返回值来比较元素之间的大小, 然后进行“升序”排列,这种排序方式我们称之为自然排列。
注意:如果想采用自然排序,则要存储的对象所属类必须实现Comparable 接口。该接口只有 一个方法public int compareTo(Object obj),必须实现该方法。
compareTo方法的实现规则:
返回 0,表示 this == obj。//则不会添加新对象(this)
返回正数,表示 this> obj //添加到原来对象(obj)的右边
返回负数,表示 this < obj // 添加到原来对的左边
4,演示代码
package Java基础.常用类.集合.set;
import java.util.Comparator;
import java.util.Scanner;
import java.util.TreeSet;
public class Done5 {
public static void main(String[] args) {
TreeSet<StudentScore> set = new TreeSet();
set.add(new StudentScore("张三", 90, 85, 75));
set.add(new StudentScore("张三", 90, 85, 75));
set.add(new StudentScore("李四", 79, 88, 90));
set.add(new StudentScore("老六", 90, 65, 90));
set.add(new StudentScore("王五", 87, 65, 66));
for (StudentScore student : set) {
System.out.println(student.toString());
}
}
}
class StudentScore implements Comparable<StudentScore> {
private String name;
private double chinese, math, english;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getChinese() {
return chinese;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public double getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public double getEnglish() {
return english;
}
public void setEnglish(int english) {
this.english = english;
}
public StudentScore(String name, double chinese, double math, double english) {
this.name = name;
this.chinese = chinese;
this.math = math;
this.english = english;
}
@Override
public String toString() {
return "Student{" +
"姓名='" + name + '\'' +
", 语文成绩=" + chinese +
", 数学成绩=" + math +
", 英语成绩=" + english +
", 总成绩=" + (chinese + english + math) +
'}';
}
public double Score() {
return this.chinese + this.english + this.math;
}
@Override
public int compareTo(StudentScore o) {
int rs = (int) (o.Score() - this.Score());
if (rs == 0) {
if (o.name.equals(this.name)) {
return 0;
} else {
return -1;
}
}
return rs;
}
}
按成绩从大到小排序
5,定制排序
使用Comparable接口定义排序顺序有局限性:实现此接口的类只能按compareTo()定义的这一种方 式排序。
如果需要更加灵活地排序,我们可以自定义(Comparator)比较器,在创建TreeSet集合对象时把 我们自定义的比较器传入,则可以TreeSet会按照我们的比较器中定义的规则进行排序。
自定义比较器类,需要实现Comparator接口。
Comparator接口只有一个抽象方法需要实现: public int compare(Object a, Object b);
判断规则: 返回 0,表示a == b
返回正数,表示b > b
返回负数,表示a < b
学生类代码同上
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Student stu1 = (Student) o1;
Student stu2 = (Student) o2;
//按照年龄的降序排序
return stu2.getAge() - stu1.getAge();
}
});
treeSet.add(new Student("张三", 21));
treeSet.add(new Student("李四", 18));
treeSet.add(new Student("王五", 20));
treeSet.add(new Student("李琦", 19));
System.out.println(treeSet);
}
}
小结
-
HashSet:
- 特点:HashSet 使用哈希表来存储元素,因此它提供了快速的添加、删除和查找操作。
- 顺序:不保证元素的顺序,元素的位置取决于它们的哈希码。
- 性能:在所有 Set 实现中,HashSet 通常提供最快的性能,尤其是在添加和查询元素时。
- 唯一性:不允许包含重复元素,如果尝试添加重复元素,add 方法将返回 false。
- 空值:可以包含一个 null 元素。
- 迭代:迭代顺序是不可预测的,可能会因为哈希碰撞而变化。
-
LinkedHashSet:
- 特点:LinkedHashSet 继承自 HashSet,它使用哈希表和双向链表来存储元素。
- 顺序:维护了元素的插入顺序,即按照元素添加到集合的顺序来迭代。
- 性能:由于需要维护链表,所以比 HashSet 稍慢,但仍然提供了快速的添加、删除和查找操作。
- 唯一性:不允许包含重复元素。
- 空值:可以包含一个 null 元素。
- 迭代:迭代顺序是确定的,与元素添加的顺序一致。
-
TreeSet:
- 特点:TreeSet 实现了 SortedSet 接口,使用红黑树(一种自平衡二叉搜索树)来存储元素,因此它支持高效的顺序访问和排序操作。
- 顺序:元素按照自然顺序或者自定义比较器排序,确保元素处于排序状态。
- 性能:在添加、删除和查找操作上比 HashSet 和 LinkedHashSet 慢,因为它需要维护元素的顺序。
- 唯一性:不允许包含重复元素。
- 空值:不支持包含 null 元素,因为 null 无法进行排序。
- 迭代:迭代时按照元素的排序顺序进行。
总结:
- 如果不需要关心元素的顺序,且对性能有较高要求,应该使用 HashSet。
- 如果需要维护元素的插入顺序,并且愿意为此付出一定的性能代价,应该使用 LinkedHashSet。
- 如果需要元素按照一定的顺序排列,应该使用 TreeSet,并且可以提供自定义的比较器来定义排序规则。