java.util.Set接口和java.util.List接口是一样的,都是继承自Collection接口,它与Collection接口中的方法基本一样,没有对Collection接口进行功能上的扩展,只是比Collection接口更加严厉。与List接口不同的是,Set接口中的元素是无序的,并且都会以某种规则存入的元素不重复
- Set接口有很多个子类,我们主要介绍两个重要子类:java.util.HashSet和java.util.LinkedHashSet集合
- Set集合取出的元素的方式可以采用:迭代器、增强for循环
HashSet集合介绍
- java.util.HashSet是Set接口的一个实现类,它存储的元素是不可重复的,并且元素都是一个无需的(存取顺序不一致)。java.util.HashSet底层的实现其实是一个java.util.HashMap支持的。
HashSet
是根据对象的哈希值来确定元素在集合当中的存储位置,因此它具有良好的存取和查找性能。保证元素唯一性的方式依赖于hashCode
和equals
方法
实例代码:
/**
* java.util.Set.extends Collection接口
* Set接口的特点:
* 1、不允许存储重复性的元素
* 2、没有索引,没有带索引的方法,也不能使用普通的for循环进行遍历
* java.util.HashSet implements Set 接口
* HashSet集合的特点:
* 1、不允许存储重复性的元素
* 2、没有索引,没有带索引的方法,也不能使用普通的for循环进行遍历
* 3、是一个无序的集合,存储的元素顺序和取出元素的顺序可能不一致
* 4、底层是一个哈希结构(查询)
*/
public class SetDemo01 {
public static void main(String[] args) {
//构建Set集合
Set<Integer> set = new HashSet<>();
//使用add方法往集合当中存储一些元素
set.add(123);
set.add(125);
set.add(120);
set.add(123);
//查看set集合当中的元素
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()){
Integer num = iterator.next();
System.out.println(num);
}
System.out.println("------------------------");
for (Integer integer : set) {
System.out.println(integer);
}
}
HashSet集合存储数据的结构(哈希表)
-
什么是哈希表呢?
-
在JDK1.8之前,哈希表的底层采用的是数组+链表实现,即使用链表处理哈希冲突,同一哈希值的链表都存储在一个链表里,但是当位于一个链中的元素较多时,即hash值相等的元素较多时,通过key值依次查找的效率很低下。在JDK1.8中,哈希表存储结构采用数组+链表/红黑树实现的,当链表的长度超过阈值(8)时,将链表转换成红黑树结构,这样的好处是大大减少了查找的时间
如图展示:
-
总而言之,JDK1.8之后引入红黑树结构大大优化了HashMap的性能,那么对于我们来讲保证HashSet元素唯一不重复,其实是根据对象的hashCode和equals方法来决定的。如果我们往集合当中存储的是自定义的对象,需要保证对象的唯一性,就必须重写HashCode和equals方法,来自定义当前对象的比较方式。
HashSet存储自定义类型的元素
- 一般需要重写对象当中的hashCode和equals方法,建立自己的比较方式。才能保证HashSet集合中元素的唯一性。
代码例子如下:
例如自己构建一个对象:
没有重写equals方法和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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
进行比较对象:
//创建一个HashSet集合对象
HashSet<Student> hashSet = new HashSet<>();
//构建多个学生对象
Student s1 = new Student("小孙", 21);
Student s2 = new Student("小孙", 21);
Student s3 = new Student("小孙", 21);
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
System.out.println(s1==s2);//fasle
System.out.println(s1.equals(s2));//false
hashSet.add(s1);
hashSet.add(s2);
hashSet.add(s3);
System.out.println(hashSet);
}
结果如下:
重写后equals方法和hashcode方法后的比较
代码如下:
//自定义的Student类重写的hashCode和equals方法
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (o instanceof Student) {
Student student = (Student) o;
//同名同年龄的人为同一个人 true
return student.getName().equals(name) && student.getAge() == age;
}
return false;
}
@Override
public int hashCode(){
//使用工具类objects中的hash方法
return Objects.hash(name,age);
}
演示结果如下: