【JAVA基础】集合类之HashSet的原理及应用

news2024/10/8 14:27:02

近期几期内容都是围绕该体系进行知识讲解,以便于同学们学习Java集合篇知识能够系统化而不零散。
在这里插入图片描述

本文将介绍HashSet的基本概念,功能特点,使用方法,以及优缺点分析和应用场景案例。

一、概念

HashSet是 Java 集合框架中的一个重要成员,它实现了Set接口。Set接口的主要特点是不允许包含重复元素,而HashSet以哈希表的方式来存储元素,这使得它在存储和检索元素时具有高效的性能。

与LinkedHashSet 的区别:

  • HashSet 底层是由HashMap实现的,通过对象的hashCode方法与equals方法来保证插入元素的唯一性,无序(存储顺序和取出顺序不一致),。
  • LinkedHashSet 底层数据结构由哈希表和链表组成。哈希表保证元素的唯一性,链表保证元素有序。(存储和取出是一致)

二、存储方式

哈希函数

(1)HashSet使用哈希函数来计算元素的哈希值。哈希函数是一种将任意长度的数据映射为固定长度哈希值的函数。对于要存储的每个元素,HashSet会调用其哈希函数来获取一个哈希值。
(2)例如,对于整数类型,哈希函数可能会简单地对整数进行一些计算来得到哈希值;对于对象类型,默认会使用对象的hashCode()方法来计算哈希值。

哈希表结构

(1)HashSet内部使用哈希表来存储元素。哈希表是一个数组,每个数组元素称为一个 “桶”(bucket)。
(2)当一个元素被添加到HashSet时,首先通过哈希函数计算出该元素的哈希值,然后根据哈希值确定它应该存储在哪个桶中。
(3)如果多个元素的哈希值相同,它们会被存储在同一个桶中。在这种情况下,HashSet会通过比较元素的equals()方法来确保集合中不包含重复元素。只有当两个元素的哈希值相同且equals()方法返回true时,才被认为是重复元素。

三、源码分析

构造方法

HashSet()
构造一个新的空 set,其底层 HashMap 实例的默认初始容量是 16,加载因子是 0.75。
HashSet(Collection<? extends E> c)
构造一个包含指定 collection 中的元素的新 set。
HashSet(int initialCapacity)
构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和默认的加载因子(0.75)。
HashSet(int initialCapacity, float loadFactor)
构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和指定的加载因子。 放

实现原理

(1)往Haset添加元素的时候,HashSet会先调用元素的hashCode方法得到元素的哈希值 ,然后通过元素 的哈希值经过移位等运算,就可以算出该元素在哈希表中的存储位置。见下面2种情况:
情况1: 如果算出元素存储的位置目前没有任何元素存储,那么该元素可以直接存储到该位置上。
情况2: 如果算出该元素的存储位置目前已经存在有其他的元素了,那么会调用该元素的equals方法与该位置的元素再比较一次,如果equals返回的是true,那么该元素与这个位置上的元素就视为重复元素,不允许添加,如果equals方法返回的是false,那么该元素运行 添加。

HashSet 中 hashCode () 与 equals () 方法的调用时机

添加元素时

  • 哈希值计算与哈希冲突判断
    当向HashSet中添加一个元素时,首先会调用该元素的hashCode()方法来获取其哈希值。这个哈希值用于确定元素在HashSet内部哈希表中的存储位置(桶的位置)。
    例如,有一个自定义类Person的实例要添加到HashSet中:
public class Person {
    int id;
    String name;
    public Person(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
    @Override public String toString() {
        return "{ 编号:" + this.id + " 姓名:" + this.name + "}";
    }
    @Override public int hashCode() {
        System.out.println("=======hashCode方法被调用了=====");
        return this.id;
    }
    @Override public boolean equals(Object obj) {
        System.out.println("======equals方法被调用了======");
        Person p = (Person) obj;
        return this.id == p.id;
    }
}

原始的判断是否能添加的逻辑:
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该HashCode值决定该对象在HashSet中的存储位置。如果有两个元素通过equals()方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同的位置,依然可以添加成功。即,HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。

当执行 set.add(new Person(110, “Alice”));时,会先调用Person类的hashCode()方法计算哈希值
在上面HashCode是自己重写过的,将ID直接当做Hash值;
同时重写了equals方法,因为add的时候 先调用HashCode方法判断Hash值是否一致,如果一致,则通过判断equals的结果是否为true,如果为true则代表元素重复,拒绝添加!
重写的equals方法:如果ID相同则元素相同

如果不同元素计算出的哈希值相同(哈希冲突),此时HashSet会进一步调用这些元素的equals()方法来判断它们是否真正相等。只有当两个元素的哈希值相同且equals()方法返回true时,才认为这两个元素是重复的,不会将新元素添加到HashSet中。
假设在HashSet中已经存在一个Person对象p1( id= 25,name = “Alice”),现在要添加另一个Person对象p2【new Person(220, “GOD”)】 p3对象【new Person(330, “LiMing1”)】、p4对象【new Person(110, “LiMing2”)】;

验证:


class Demo2 {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        boolean alice = set.add(new Person(110, "Alice"));
        boolean god = set.add(new Person(220, "GOD"));
        boolean liMing1 = set.add(new Person(330, "LiMing1"));
        boolean liMing2 = set.add(new Person(110, "LiMing2"));
        //在现实生活中只要编号一致就为同一个人.
        System.out.println("添加成功吗?" +liMing1);
        System.out.println("添加成功吗?" +liMing2 );
        System.out.println("集合的元素:" + set);

    }
}

可见结果如下:
解释:
因为add了四次,所以有调用四次hashCode方法,来计算HashCode,在我们的代码里面重写了HashCode方法,id当做了HashCode的值,如果HashCode的值一致,则调用equals方法,判断id是否一致,如果一致则判断元素重复,添加失败。因为有两个110的id,所以第一个添加成功,第二个添加失败
在这里插入图片描述
如果id改完不一致的,则添加成功。即使name一致(因为重写的equals是通过判断ID是否一致来判断元素重复与否的。)
在这里插入图片描述

四、使用场景

去重

当需要去除一个集合中的重复元素时,HashSet是一个很好的选择。例如,在处理一组用户输入的数据时,如果不希望有重复的数据,可以将数据存储在HashSet中。


class Demo2 {
    public static void main(String[] args) {
   
        System.out.println("=======================================");
//      LinkedHashSet去重  去重后保持原有顺序(重复数据只保留一条)
        String[] arr = new String[] {"i", "think", "i", "am", "the", "best"};
        Collection<String> noDups = new LinkedHashSet<String>(Arrays.asList(arr));
        System.out.println("(LinkedHashSet) distinct words:    " + noDups);
        System.out.println("=======================================");
//       去重后顺序打乱(重复数据只保留一条)
        String[] arr2 = new String[] {"i", "think", "i", "am", "the", "best"};
        Collection<String> noDups2 = new HashSet<String>(Arrays.asList(arr2));
        System.out.println("(HashSet) distinct words:    " + noDups2);
        System.out.println("=======================================");
//      去重后顺序打乱(重复数据只保留一条)
        String[] arr3 = new String[] {"i", "think", "i", "am", "the", "best"};
        Set<String> s = new HashSet<String>();
        for (String a : arr3)
        {
            if (!s.add(a))
            {
                System.out.println("Duplicate detected: " + a);
            }
        }
        System.out.println(s.size() + " not distinct words: " + s);
//        去重后顺序打乱(相同的数据一条都不保留,取唯一) ,能把重复的元素剔除出去;同时把哪些元素重复过滤出来
         System.out.println("=======================================");
        String[] arr4 = new String[] {"i", "think", "i", "am", "the", "best"};
        Set<String> uniques = new HashSet<String>();
        Set<String> dups = new HashSet<String>();
        for (String a : arr4)
        {
            {
                if (!uniques.add(a))
                    dups.add(a);
            }
        }
        uniques.removeAll(dups);
        System.out.println("Unique words:    " + uniques);
        System.out.println("Duplicate words: " + dups);




    }
}

结果:
在这里插入图片描述

快速查找

由于哈希表的特性,HashSet在查找元素时具有非常高的效率。平均时间复杂度接近常数时间。
比如在一个大型数据集中快速判断某个元素是否存在

集合运算

在进行一些集合相关的运算时,HashSet也很有用。例如,可以使用HashSet来计算两个集合的交集、并集和差集等。

假设有两个集合set1和set2,计算它们的交集:

Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Set<Integer> set2 = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));

Set<Integer> intersectionSet = new HashSet<>(set1);
intersectionSet.retainAll(set2);
System.out.println("Intersection set: " + intersectionSet); // 输出 [4, 5]

在多线程环境下的使用示例(注意需要适当的同步措施)

import java.util.HashSet;
import java.util.Set;

public class HashSetInMultiThreadExample {
    public static void main(String[] args) {
        Set<Integer> sharedSet = new HashSet<>();

        // 创建多个线程并向集合中添加元素
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                sharedSet.add(i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1000; i < 2000; i++) {
                sharedSet.add(i);
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        // 等待线程完成
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出集合中的元素数量
        System.out.println("Size of the set after multi-threaded operations: " + sharedSet.size());
    }
}

需要注意的是:HashSet是线程不安全的,如果涉及到多线程需要使用ConcurrentHashSet;
在这个多线程示例中,如果不进行适当的同步,可能会导致数据不一致等问题。在实际应用中,可以考虑使用ConcurrentHashSet等线程安全的集合类或者通过其他同步机制来确保线程安全。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2196548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Boot实现License生成与校验详解

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 在软件开发领域&#xff0c;License&#xff08;许可证&#xff09;机制是保护软件版权、控制软件使用范围的重要手段。通过为软件生成唯一的License&#xff0c;开发者可以确保只有合法用户才能使用软件&…

右键菜单添加 Open Tabby here

如果安装了Tabby&#xff0c;为了提高效率在鼠标右键菜单中添加Open Tabby here&#xff0c;可以通过按 win R&#xff0c;并输入regedit 回车打开注册表编辑器 计算机\HKEY_CLASSES_ROOT\Directory\Background\shell 然后在Shell下面新建项&#xff0c;名称为Tabby&#xf…

企业架构理论TOGAF从理论到实践:引领企业数字化转型的实践指南

在现代企业面临的数字化转型浪潮中&#xff0c;如何从战略层面实现技术与业务的全面融合&#xff0c;成为了众多企业的核心挑战。TOGAF&#xff08;The Open Group Architecture Framework&#xff09;不仅为企业提供了强大的理论框架&#xff0c;还通过实践验证了其在推动企业…

力扣 中等 39.组合总和

文章目录 题目介绍解法 题目介绍 解法 是216组合总和III链接的扩展 class Solution {List<List<Integer>> res new ArrayList<>();List<Integer> path new ArrayList<>();public List<List<Integer>> combinationSum(int[] can…

Windows 下安装mamba_ssm 记录,包括causal-conv1d和mamba-ssm

Windows 下安装mamba_ssm 记录 1 重要参考文献2 具体安装步骤3 一些提醒事项4 安装causal-conv1d5 安装mamba-ssm6 结果展示 1 重要参考文献 Window 下Mamba 环境安装踩坑问题汇总及解决方法 2 具体安装步骤 重点看的是这篇 Window 下Mamba 环境安装踩坑问题汇总及解决方法 …

【Redis】持久化(下)-- AOF

文章目录 AOF概念如何使用AOFAOF工作流程命令写入演示文件同步策略 AOF的重写机制概念触发重写机制AOF重写流程 启动时数据恢复混合持久化总结 AOF 概念 AOF持久化:以独立日志的方式记录每次的写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的.AOF的主要作用是解决…

工行企业网银U盾展期后有两个证书问题的解决方法

工行企业网银U盾证书快到期后&#xff0c;可以自助展期&#xff0c;流程可以根据企业网银提示页面操作。操作后&#xff0c;可能存在两个新旧两个证书并存的情况&#xff0c;致使网银转账等操作失败&#xff0c;如图&#xff1a; 其原因是新证书生成后&#xff0c;旧证书没有删…

wsl配置图形显示环境 no $display environment variable

wsl运行fsl&#xff0c;安装好之后&#xff0c;可以使用bet&#xff0c;等命令行进行操作&#xff0c;但是不能使用fsl呼出窗口。 因为 wsl并不像原生linux具有destop桌面&#xff0c;它只有命令行。所以当运行fsl的时候会报错&#xff0c; application-specific initializat…

裁掉数千人、把工作外包给 AI!一年多后,这家巨头的 CEO恳求无人搭理

“对&#xff0c;裁掉几千名员工。” “好的&#xff0c;头儿。” “很好&#xff0c;那么这个人工智能可以做那些前雇员能做的一切事情&#xff1f;” “不&#xff0c;不全是。” “等等&#xff0c;什么&#xff1f;” “你刚刚裁掉的几百人都是硬件工程师&#xff0c;…

k8s的pod的管理和优化

资源管理介绍 在kubernetes中&#xff0c;所有的内容都抽象为资源&#xff0c;用户需要通过操作资源来管理kubernetes。 kubernetes的本质上就是一个集群系统&#xff0c;用户可以在集群中部署各种服务 所谓的部署服务&#xff0c;其实就是在kubernetes集群中运行一个个的容器…

Kubernetes--深入理解Pod资源管理

文章目录 kubectl --helpapi-resourcesapi-versionskubectl explain ... API资源资源规范PodServiceConfigMapSecret 显示资源删除资源详细描述RESTful API Pod资源管理Pod的核心概念Pod资源配置了解Pod运行状况Kubectl get pods xxxxkubectl describe pods xxxkubectl logs -f…

如何彻底掌握 JavaScript 23种设计模式

设计模式是解决特定问题的常用解决方案&#xff0c;它们可以帮助开发者编写更清晰、可维护、可扩展的代码。在 JavaScript 中&#xff0c;常见的设计模式可以分为三大类&#xff1a;创建型模式、结构型模式 和 行为型模式。本文将全面介绍 JavaScript 中常见的设计模式&#xf…

性能剖析利器-Conan|得物技术

作者 / 得物技术 - 仁慈的狮子 目录 一、背景 1. 局限性 2. 向前一步 二、原理剖析 1. 系统架构 2. 工作模式 3. reporter 三、稳定性验证 四、案例分析 五、写在最后 一、背景 线上问题的定位与优化是程序员进阶的必经之路&#xff0c;常见的问题定位手段有日志排查、分布式链…

脑机接口技术的未来与现状:Neuralink、机械手臂与视觉假体的突破

近年来&#xff0c;脑机接口&#xff08;BCI&#xff09;技术发展迅速&#xff0c;不仅限于科幻小说和电影&#xff0c;已经逐步进入现实应用。特别是马斯克的Neuralink公司推出的“盲视&#xff08;Blindsight&#xff09;”设备&#xff0c;最近获得了FDA的突破性设备认定&am…

IEC104规约的秘密之八----应用任务优先级

所谓应用任务优先级&#xff0c;就是同时出现不同的应用任务时&#xff0c;优先发哪个报文。这里有一个表格&#xff0c;可以做为参考&#xff0c;一般是在子站来实现&#xff0c;子站是数据提供方&#xff0c;需要对各种任务的优先级进行排序&#xff0c;以满足应用的实际需要…

为什么Linux系统下的程序无法在Windows下运行

两个系统的格式不同&#xff0c;格式就是协议&#xff0c;是在固定位置有意义的数据。Linux下可执行文件格式是elf&#xff0c;可使用readelf查看elf文件头 而Windows下的可执行程序是PE格式&#xff0c;是一种可执行文件。 还有一点是Linux下和Win下系统API不同&#xff0c;这…

【CSS】houdini自定义CSS属性实现渐变色旋转动画

现有一段代码&#xff0c;在不旋转整个元素的前提下&#xff0c;渐变背景无法应用动画 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initia…

基于 TOSHIBA eFuse 应用电路(带热关断功能)设计方案

近年来各类消费产品&#xff0c;存储设备&#xff0c;服务器等电路变得越来越密集&#xff0c;越来越灵敏&#xff0c;因此保护功能变得越来越重要&#xff0c;我们开发了是用于过流保护和过温保护的参考设计解决方案。 将介绍参考设计中的两种电路&#xff0c;合在一起2CM*2CM…

jetlinks物联网平台学习5:dtu设备接入及温度报警场景联动

dtu设备接入及温度报警场景联动 1、平台端配置1、新建协议2、新建网络组件3、设备接入网关配置4、新增产品5、导入产品物模型6、新增设备7、场景联动配置7.1、触发规则7.2、触发条件7.3、执行动作 2、平台端验证场景联动 1、平台端配置 下载三个文件 https://hanta.yuque.com…

详解 SPI 机制

SPI(Service Provider Interface) 是 JDK 内置的一种服务提供发现机制&#xff1a;可以用来启用框架扩展和替换组件&#xff0c;主要用于框架中开发。例如&#xff1a;Dubbo、Spring、Common-Logging&#xff0c;JDBC 等都是采用 SPI 机制&#xff0c;针对同一接口采用不同的实…