【集合系列】HashSet 集合

本文详细介绍了Java中的HashSet集合,包括其无序性、不允许重复元素的特性,以及基于哈希表的高效操作。重点讲解了如何使用方法如add、remove和遍历,并强调自定义对象需重写hashCode和equals方法以确保唯一性。
摘要由CSDN通过智能技术生成

文章中的部分照片来源于哔站黑马程序员阿伟老师处,仅用学习,无商用,侵权联系删除!

其他集合类

祖父类 Collection

父类 Set

集合类的遍历方式

具体信息请查看 API 帮助文档

1. 概述

HashSet是Java中的一个集合类,它实现了Set接口。HashSet基于哈希表实现,其中的元素没有固定的顺序,且不允许包含重复的元素。

HashSet的特点

  1. 无序性:HashSet中的元素没有固定的顺序,元素在集合中的位置不是按照插入的顺序来确定的。

  2. 不允许重复元素:HashSet中不允许包含重复的元素,如果试图向HashSet中添加已经存在的元素,将会被忽略掉。

  3. 基于哈希表实现:HashSet使用了哈希表作为其内部的数据结构,对于添加、删除、查找等操作具有较高的效率。

  4. 允许存储null元素:HashSet允许存储null元素,但只能存储一个null元素。

由于HashSet是基于哈希表实现的,因此在使用HashSet时,存储的元素应该实现了hashCode()和equals()方法,以保证元素的判定和查找的正确性。

2. 方法

hashSet集合是Set集合的子类,是Collection集合的孙子类,因此Set集合和Collection集合的方法都可以使用

Collection集合

Set集合

方法名说明
boolean add(E e)添加元素
boolean remove(Object o)从集合中移除指定的元素
boolean removeIf(Object o)根据条件进行移除
void clear()清空集合中的元素
boolean contains(Object o)判断集合中是否存在指定的元素
boolean isEmpty()判断集合是否为空
int size()集合的长度,也就是集合中元素的个数

3. 遍历方式

注意】HashSet集合没有索引,因此只能用迭代器遍历、增强 for ,Lambda表达式遍历。

与共有的 集合遍历方式 一样

4. 哈希值

哈希值是将任意长度的数据转换为固定长度的唯一值的一种数值计算方法。它可以将任意长度的数据映射到固定长度的哈希码。

哈希值:对象的整数表现形式

  1. 根据hashCode方法计算出来的int类型的整数

  2. 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算

  3. 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值

哈希值的特点如下:

  1. 唯一性:哈希值通常是唯一的。即使原始数据有很小的变化,生成的哈希值也会有很大的差别。

  2. 固定长度:哈希函数将输入数据映射为固定长度的哈希值。通常情况下,不同长度的输入会生成相同长度的哈希值。

  3. 不可逆性:哈希函数是单向的,即通过哈希值无法还原得到原始数据。哈希算法是为了保护数据的安全性而设计的。

  4. 散列性:相似的原始数据会生成完全不同的哈希值。即使原始数据只是略微不同,生成的哈希值也会有很大差异。

  5. 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的

  6. 如果已经重写了hashCode方法,不同对象只要属性值相同,计算出的哈希值是相同的

  7. 但是在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)

例如:

package text.text02;

import java.util.Objects;

/*
int index=(数组长度 - 1 )& 哈希值;
哈希值:对象的整数表现形式
    1.根据hashCode方法计算出来的int类型的整数
    2.该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
    3.一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值

特点:
    1.如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
    2.如果已经重写了hashCode方法,不同对象只要属性值相同,计算出的哈希值是相同的
    3.但是在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)

获取哈希值的方法:hashCode()
 */
public class text37 {
    public static void main(String[] args) {
        //创建未重写hashCode方法的学生类对象
        Student student1 = new Student("张三", 12);
        Student student2 = new Student("张三", 12);

        //创建重写hashCode方法的老师类对象
        Teacher1 teacher1 = new Teacher1("张三", 12);
        Teacher1 teacher2 = new Teacher1("张三", 12);

        //1.如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
        int hashCode1 = student1.hashCode();
        int hashCode2 = student2.hashCode();
        System.out.println(hashCode1);      //460141958
        System.out.println(hashCode2);      //1163157884
        System.out.println(hashCode1 == hashCode2);           //false

        //2.如果已经重写了hashCode方法,不同对象只要属性值相同,计算出的哈希值是相同的
        int hashCode3 = teacher1.hashCode();
        int hashCode4 = teacher2.hashCode();
        System.out.println(hashCode3);           //24022532
        System.out.println(hashCode4);           //24022532
        System.out.println(hashCode3 == hashCode4);       // true

        //3.但是在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)
        System.out.println("abc".hashCode());         //96354
        System.out.println("acD".hashCode());         //96354
        System.out.println("abc".hashCode() == "acD".hashCode());     //true
    }
}

//未重写hashCode方法的学生类
class Student {
    private String name;
    private int age;


    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     *
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     *
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     *
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     *
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "student{name = " + name + ", age = " + age + "}";
    }
}

//重写hashCode方法的老师类
class Teacher1 {
    private String name;
    private int age;


    public Teacher1() {
    }

    public Teacher1(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     *
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     *
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     *
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     *
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Teacher1 teacher = (Teacher1) o;
        return age == teacher.age && Objects.equals(name, teacher.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    public String toString() {
        return "teacher{name = " + name + ", age = " + age + "}";
    }
}

5. 哈希表

hashSet集合底层采取哈希表存储数据。

  • JDK8以前:数组 + 链表
    在这里插入图片描述

  • JDK8以后:数组 + 链表 + 红黑树
    在这里插入图片描述

当链表长度大于8而且数组长度大于等于64时,会将链表转换为红黑树

底层原理

  1. 创建一个默认长度16,默认加载因子为0.75的数组,数组名为table

  2. 根据元素的哈希值跟数组的长度计算出应有的位置 int index=(数组长度 - 1 )& 哈希值;

  3. 判断当前位置是否为null,如果是null直接存入

  4. 如果位置不是null,表示有元素,则调用equals方法比较属性值

  5. 一样:不存 不一样:存入数组,形成链表

    • JDK8以前:新元素存入数组,老元素挂在新元素下面

    • DK8以后:新元素直接挂在老元素下面

如果集合中存储的是自定义对象,必须重写hashCodeequals方法

代码示例:

需求:创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象

package text.text02;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;

/*
hashSet集合:无序,不重复,无索引

hashSet集合底层采取哈希表存储数据。
JDK8以前:数组 + 链表
JDK8以后:数组 + 链表 + 红黑树

底层原理:
    1.创建一个默认长度16,默认加载因子为0.75的数组,数组名为table
    2.根据元素的哈希值跟数组的长度计算出应有的位置   int index=(数组长度 - 1 )& 哈希值;
    3.判断当前位置是否为null,如果是null直接存入
    4.如果位置不是null,表示有元素,则调用equals方法比较属性值
    5.一样:不存     不一样:存入数组,形成链表
        JDK8以前:新元素存入数组,老元素挂在新元素下面
        JDK8以后:新元素直接挂在老元素下面

利用hashSet集合去除重复元素

 */
public class text38 {
    public static void main(String[] args) {
        //创建集合
        HashSet<Student2> hashSet = new HashSet<>();
        //添加学生对象并打印结果
        System.out.println(hashSet.add(new Student2("张三", 23)));//true
        System.out.println(hashSet.add(new Student2("王五", 24)));//true
        System.out.println(hashSet.add(new Student2("李四", 27)));//true
        System.out.println(hashSet.add(new Student2("张三", 23)));//false

        //遍历集合
        Iterator<Student2> it = hashSet.iterator();
        while (it.hasNext()) {
            Student2 str = it.next();
            System.out.print("[" + str.getName() + "," + str.getAge() + "]  ");    //[张三,23]  [李四,27]  [王五,24]  
        }
    }
}

//重写hashCode方法和equals方法的学生类(不重写hashCode和equals方法,比较的是地址值;重写后,比较的属性值)
class Student2 {
    private String name;
    private int age;


    public Student2() {
    }

    public Student2(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     *
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     *
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     *
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     *
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student2 student2 = (Student2) o;
        return age == student2.age && Objects.equals(name, student2.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    public String toString() {
        return "Student2{name = " + name + ", age = " + age + "}";
    }
}

6. 注意事项

  1. 唯一性:HashSet不允许包含重复的元素。这是通过对元素的哈希值进行比较来实现的。因此,如果要将自定义对象添加到HashSet中,需要确保对象正确实现了hashCode()和equals()方法,以便正确判断元素的唯一性。

  2. 不保留顺序:HashSet中的元素没有固定的顺序。即使在添加元素的顺序不变的情况下,HashSet在输出时也不保证元素的顺序。如果需要维护元素的插入顺序,可以考虑使用LinkedHashSet。

  3. 可以存储null元素:HashSet允许存储null元素,但只能存储一个null元素。尝试添加多个null元素时,只会保留一个,其他的将会被忽略掉。

  4. 性能:由于HashSet是基于哈希表实现的,对于添加、删除和查找操作具有较高的性能。但是,哈希表的性能会受到哈希冲突的影响,因此在使用HashSet时,尽量避免出现大量的哈希冲突,可以通过合理设计hashCode()方法来减少冲突的发生。

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷小洋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值