Java 集合 - Set 接口

news2024/11/28 12:56:03

文章目录

    • 1.概述
    • 2.HashSet
    • 3.LinkedHashSet
    • 4.TreeSet
    • 5.选择合适的 Set 实现
    • 6.总结

1.概述

Set 接口的定义非常简单。它本质上是一个 Collection,但是要求该集合不能有重复的元素。换句话说,如果尝试将一个元素添加到 Set 中,而该元素已经存在于 Set 中,那么 add 方法将返回 false,并且 Set 本身不会改变。

Java为 Set 接口提供了几个主要的实现:

  • HashSet: 基于哈希表的 Set 实现,它不保证集合的迭代顺序;尤其是它不保证该顺序恒久不变。
  • LinkedHashSet: 哈希表和链表实现的 HashSet,具有可预知迭代顺序
  • TreeSet: 基于树(红黑树)的 Set 实现,按照元素的自然顺序进行排序,或者根据创建集合时提供的比较器进行排序

2.HashSet

Java中的 Set 接口有几个实现,但是最常用的无疑是 HashSet。我们经常默认地使用它。

HashSetSet 接口的一种基础实现,被广泛应用于各种程序中。

其类图如下:

根据其源码声名,我们可以看到 HashSet 实际上是由 HashMap 支持的,HashMap 则是基于哈希表的实现。这种数据结构设计,使得 HashSet 具有出色的存取和查找性能

哈希表是一种数据结构,它提供了快速的元素插入和查找操作。在 HashSet 中,通过哈希算法来确定元素在哈希表中的位置。这意味着,无论 HashSet 中有多少元素,确定元素是否存在(以及获取这个元素)的时间大致是常数 O(1) 的,这是 HashSet 高效性能的主要来源。

HashSet 确定两个元素相等的标准是:两个对象的 hashCode() 方法返回值相等,并且两个对象的 equals() 方法返回结果也相等。hashCode() 方法用于确定元素在哈希表中的位置,而 equals() 方法则用于在哈希冲突时比较元素的实际值。这意味着,如果你要将自己的对象存储在 HashSet 中,那么你应该重写这两个方法,以确保它们的行为符合 HashSet 的需求。

下面是一个简单的重写 hashCode()equals() 方法的案例:

public class MyDate {
    private int year;
    private int month;
    private int day;

    @Override
    public boolean equals(Object o){
        System.out.println("调用equals()方法");
        
        // 如果对象地址一样,则认为相同
        if (this == o) return true;
        // 如果参数为空,或者类型信息不一样,则认为不同
        if (!(o instanceof MyDate)) return false;
        // 转换为当前类型
        MyDate myDate = (MyDate) o;
        // 使用 == 比较基本类型,使用 equals 比较引用类型(此处没有必要)
        return year == myDate.year && month == myDate.month && day == myDate.day;
    }

    @Override
    public int hashCode(){
        System.out.println("调用hashCode()方法");
        
        // Objects类的hash方法返回一个int类型的值,作为哈希值
        return Objects.hash(year, month, day);
    }

    @Override
    public String toString(){
        return "MyDate{" + "year=" + year + ", month=" + month + ", day=" + day + '}';
    }

    // 省略构造器、getter和setter方法
}

下面进行基本的测试:

public class TestHashSet {
    public static void main(String[] args) {
        // 创建HashSet集合
        HashSet<String> set = new HashSet<>();
        // 添加元素
        set.add("Java");
        set.add("Java"); // 重复元素
        set.add("Python");
        set.add("C");
        // 输出集合(不保证顺序)
        System.out.println(set);

        // 创建HashSet集合
        HashSet<MyDate> set1 = new HashSet<>();
        // 添加元素
        set1.add(new MyDate(2020, 1, 1));
        set1.add(new MyDate(2020, 1, 1)); // 重复元素
        set1.add(new MyDate(2020, 1, 2));

        // 输出集合(不保证顺序)
        System.out.println(set1);
    }
}

输出结果分析:

调用hashCode()方法
调用hashCode()方法
调用equals()方法
调用hashCode()方法
[MyDate{year=2020, month=1, day=2}, MyDate{year=2020, month=1, day=1}]

从上面的结果来看,我们发现在执行添加操作时会自动调用 hashCode() 方法通过 Hash 算法为该元素设置一个 Hash 值以确定在哈希表中的存储位置。当添加重复元素时同样先调用 hashCode() 方法通过 Hash 算法为该元素设置一个 Hash 值,此时发现该哈希值已经存在,于是自动调用 equals() 方法进行进一步的比较,如果判定是同一个元素则不进行添加操作。

可见,通过使用 HashMap 作为其内部结构,HashSet 利用了哈希表的性能优势。不仅如此,它也遵循了一种非常强大的对象相等性检查策略。这使得 HashSet 成为 Java 集合中高效、可靠的选项,无论是在性能还是在语义上,都使得 HashSet 成为实现 Set 接口的理想选择。

3.LinkedHashSet

在Java的集合框架中,LinkedHashSet 是一个特殊的 Set 实现,它继承自 HashSet 并提供了一些额外的特性。

LinkedHashSetHashSet 的一个扩展子类,其类图如下:

HashSet 是基于哈希表实现的,提供了出色的元素插入和查找性能。然而,它并没有保留元素的插入顺序,这在某些场景下可能是一个缺点。这就是 LinkedHashSet 被引入的原因。它在 HashSet 的基础上,添加了两个指针域 beforeafter,用于链接各个元素节点,从而记录了元素的添加顺序。

因此,LinkedHashSet 实际上是链表和哈希表的组合结构。链表保持了元素的插入顺序,而哈希表保证了快速的元素插入和查找性能。这种结构使得 LinkedHashSet 不仅继承了 HashSet 的高性能,同时还能提供可预测的迭代顺序

在插入性能上,由于 LinkedHashSet 需要维护额外的链表,所以其性能略低于 HashSet。然而,这种性能下降通常是可以接受的,特别是在需要保持插入顺序的场景下。

在迭代访问性能上,LinkedHashSet 表现得非常出色。由于它维护了一个运行在插入顺序上的链表,所以在遍历 Set 的所有元素时,LinkedHashSet 提供了高效且稳定的性能。这使得它在需要频繁迭代的应用场景下,成为一个非常好的选择。

下面是一个简单的使用案例:

public class LinkedHashSetTest {
    public static void main(String[] args) {
        LinkedHashSet<String> set = new LinkedHashSet<>();
        
        // 添加元素
        set.add("Java");
        set.add("Java"); // 重复元素
        set.add("Python");
        set.add("C");

        // 输出集合(保证顺序)
        System.out.println(set); // [Java, Python, C]
    }
}

4.TreeSet

TreeSet 是 Java 集合框架中的一个重要成员,它提供了一种以排序和去重为核心特性的集合。

TreeSet 的底层是基于 TreeMap 实现的,TreeMap 的底层数据结构是红黑树,一种自平衡的二叉搜索树。由于红黑树的性质,TreeSet 能够高效地进行元素的插入、删除和查找操作,同时保证了元素的排序

其类图如下:

TreeSet 的两大核心特性是元素的去重和排序

去重的逻辑主要取决于比较元素的方式。TreeSet 支持两种比较方式,分别是自然排序和定制排序。

  • 对于自然排序,TreeSet 要求集合元素实现 Comparable 接口,并重写 compareTo 方法。当 TreeSet 在添加新元素时,会调用该元素的 compareTo 方法与已有元素进行比较。如果返回值为 0,表示两个元素相等,新元素就不会被添加到 TreeSet 中。
  • 对于定制排序,TreeSet 在创建时需要指定一个实现了 Comparator 接口的对象。当添加新元素时,TreeSet 会调用 Comparatorcompare 方法进行元素比较。同样,如果 compare 方法返回0,新元素就不会被添加到 TreeSet 中。

对于排序,TreeSet 支持自然排序和定制排序两种方式:

  • 自然排序:TreeSet 要求集合元素实现 Comparable 接口并重写 compareTo 方法。compareTo 方法的返回值决定了元素的排列顺序。
  • 定制排序:创建 TreeSet 对象时,可以通过构造函数传入一个 Comparator 对象。Comparator 接口中的 compare 方法将被用于元素的排序。

下面是一个简单实用案例:

public class TreeSetTest {
    public static void main(String[] args) {
        /*
         * 默认情况下采用自然排序,会调用 Comparable 接口中的 compareTo 方法进行比较
         * 1.对于字符串:按照 Unicode 编码值的大小进行比
         * 2.对于自定义类型:需要实现 Comparable 接口,重写 compareTo 方法
         * 3.对于整形:按照数值大小进行比较
         * 4.对于浮点型:按照数值大小进行比较
         * 5.对于布尔型:false < true
         */
        TreeSet<String> set = new TreeSet<>();

        // 添加元素
        set.add("Java");
        set.add("Java"); // 重复元素
        set.add("Python");
        set.add("C");
        set.add("C++");
        set.add("Go");
        set.add("C#");

        // 输出集合
        System.out.println(set); // [C, C#, C++, Go, Java, Python]
    }
}
public class TreeSetTest02 {
    public static void main(String[] args) {
        // 如果是定制排序,需要在创建 TreeSet 时传入 Comparator 接口的实现类对象,重写 compare 方法去自定义排序规则
        TreeSet<String> set = new TreeSet<>((o1, o2) -> {
            // 按照字符串长度比较
            return o1.length() - o2.length();
        });

        // 添加元素
        set.add("Java");
        set.add("Python");
        set.add("C");
        set.add("C++");
        set.add("Go");

        // 输出集合
        System.out.println(set); // [C, Go, C++, Java, Python]
    }
}

5.选择合适的 Set 实现

选择哪种 Set 实现,主要取决于你的具体需求:

  • 如果你只是需要一个不包含重复元素的集合,并不关心元素的顺序,那么 HashSet 是一个很好的选择。它提供了常数时间的基本操作(add、remove 和 contains)。
  • 如果你关心元素的插入顺序,那么 LinkedHashSet 是一个更好的选择。它在 HashSet 的基础上,使用链表维护了元素的插入顺序。
  • 如果你需要一个排序的集合,那么 TreeSet 是最好的选择。它使用红黑树来存储元素,提供了有序的集合视图。

6.总结

下面是一个简单的总结表格:

特性HashSetLinkedHashSetTreeSet
元素的顺序无序插入顺序有序
允许 null
性能中等较低
基于HashMapLinkedHashMapTreeMap
特殊功能记录插入顺序排序

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

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

相关文章

FPGA实现ESP8266驱动且进行数据包收发

一. 简介 本次将使用正点原子的ESP8266 WIFI模块&#xff0c;来实现PC与FPGA之间的TCP通讯&#xff0c;其中ESP8266与FPGA之间的接口是UART。 二. 正点原子的ESP8266 WIFI模块介绍 模块实物图如下&#xff0c;到手就可以使用了&#xff0c;RST和IO_0两个IO口不接或者接高电平…

C++布隆过滤器和哈西切分

文章目录 一、布隆过滤器的提出二、布隆过滤器的概念三、布隆过滤器的实现布隆过滤器的插入布隆过滤器的判断在不在布隆过滤器的删除布隆过滤器的优点布隆过滤器的缺点 四、布隆过滤器的应用场景五、布隆过滤器的扩展[面试题]六、哈西切分 一、布隆过滤器的提出 我们在使用新闻…

GO 语言核心编程-全文版

第 1 章 1.1Golang的学习方向 Go语言&#xff0c;我们可以简单的写成Golang. Golang开山篇 1.2Golang的应用领域 1.2.1区块链的应用开发 1.2.2后台的服务应用 1.2.3云计算/云服务后台应用 1.3学习方法的介绍 1.4讲课的方式的说明 努力做到通俗易懂注重Go语言体系&#xff…

K8s之零故障升级Pod健康探测详解

文章目录 一、Pod健康探测介绍1、三种容器探测方法2、常用三种探测探针3、探针相关属性说明 二、探测案例1、Pod启动探测案例-startupProbe2、Pod存活探测案例-livenessProbe3、Pod就绪探测案例-readinessProbe4、启动、存活、就绪探测混合使用案例 三、总结 一、Pod健康探测介…

【MySQL新手到通关】第五章 多表查询

文章目录 1. 笛卡尔积1.1 避免笛卡尔积1.2 笛卡尔积&#xff08;或交叉连接&#xff09;的理解1.3 案例分析与问题解决笛卡尔积的错误会在下面条件下产生&#xff1a; 2. 多表查询分类讲解2.1 多表联查分类方式1&#xff1a;2.2 多表联查分类方式2&#xff1a;2.3 多表联查分类…

Eclipse教程 Ⅴ

Eclipse 创建 Java 类 打开新建 Java 类向导 你可以使用新建 Java 类向导来创建 Java 类&#xff0c;可以通过以下途径打开 Java 类向导&#xff1a; 点击 "File" 菜单并选择 New > Class在 Package Explorer 窗口中右击鼠标并选择 New > Class点击类的下拉…

c++输入输出文件操作stream

系列文章目录 C IO库 文章目录 系列文章目录前言一、文件IO概述coutcin其他istream类方法 文件输入和输出内核格式化总结 前言 一、文件IO 概述 c程序把输入和输出看作字节流。输入时&#xff0c;程序从输入流中抽取字节&#xff1a;输出时&#xff0c;程序将字节流插入到输…

springboot+ssm+java校园二手物品交易系统vxkyj

样需要经过市场调研&#xff0c;需求分析&#xff0c;概要设计&#xff0c;详细设计&#xff0c;编码&#xff0c;测试这些步骤&#xff0c;基于Java语言、Jsp技术设计并实现了校园二手物品交易系统。系统主要包括个人中心、商家管理、用户管理、商品分类管理、商品信息管理、商…

中间件SOME/IP简述

SOME/IP SOME/IP 不是广义上的中间件&#xff0c;严格的来讲它是一种通信协议&#xff0c;但中间件这个概念太模糊了&#xff0c;所以我们也一般称 SOME/IP 为通信中间件。 SOME/IP 全称是 Scalable service-Oriented MiddlewarE over IP。也就是基于 IP 协议的面向服务的可扩…

调用华为API实现身份证识别

调用华为API实现身份证识别 1、作者介绍2、调用华为API实现身份证识别2.1 算法介绍2.1.1OCR简介2.1.2身份证识别原理2.1.3身份证识别应用场景 2.2 调用华为API流程 3、代码实现3.1安装相关的包3.2代码复现3.3实验结果 1、作者介绍 雷千龙&#xff0c;男&#xff0c;西安工程大…

Spring Boot如何实现配置文件的自动加载和刷新?

Spring Boot如何实现配置文件的自动加载和刷新&#xff1f; 在使用Spring Boot开发应用程序时&#xff0c;配置文件是非常重要的组成部分。在不同的环境中&#xff0c;我们可能需要使用不同的配置文件&#xff0c;例如在开发、测试和生产环境中使用不同的配置文件。而且&#…

功能测试转到自动化测试,我的测试之路“狂飙”~20k...

前言 Python自动化测试&#xff1a;Python自动化测试&#xff0c;7天练完这60个实战项目&#xff0c;年薪过35w。 手动功能测试人员应该权衡测试自动化相对于功能测试的好处&#xff0c;并且即可开始行动。现在随着测试行业的发展&#xff0c;自动化测试已经成为每个测试人的标…

nodejs+vue大学生招聘网站应聘系统设计与实现5b14b

目前&#xff0c;伴随着Internet技术的日益成熟&#xff0c;互联网需要提供更多的服务&#xff0c;发达国家已形成以信息技术为核心&#xff0c;招聘网站支撑的现代化招聘公司技术格局。这便是今天为大家所熟悉的管理信息系统,网络发展为招聘网站实现信息化、自动化、智能化和集…

牛客小白月赛73DE

问题很好转化&#xff0c;但是对区间的处理没把握好&#xff0c;一直在纠结怎么o&#xff08;n&#xff09; 一开始想到二分了&#xff0c;但是没细想&#xff0c;结果看了讲解发现&#xff0c;其实就是一个前缀数组上对区间的查询的操作&#xff0c;以后再遇到此类问题直接向…

Git提交提交代码报错 Push failed unable to access

目录 场景 环境&#xff1a; Git配置 场景 Push failed unable to access https://github.com/1790753131/remotRepository3.git/: Failed to connect to github.com port 443 after 21114 ms: Couldnt connect to server Push failed unable to ac…

计算节点与存储设备是如何连接的?

本文是《数据存储通识课》合集的一部分,本合集希望通过一系列文章科普数据存储相关技术内容。同时,本系列文章不仅仅是科普,还会进行有深度解析,理论结合实现,从代码实现层面进行剖析。欢迎关注“数据存储张”,老张是深耕存储十几载,就业于存储No1公司的资深工程师。 无…

Keil 5 MDK 发律师函警告了,如何用STCubeIDE开发标准库的程序(STM32F103C8T6为例)

用STCubeIDE进行标准库开发 1、CubeIDE介绍 https://www.stmcu.com.cn/ecosystem/Cube/STM32CubeIDE 2、CubeIDE下载 点击上面的链接&#xff0c;登录即可下载 3、搭建Demo工程 新建一个工作空间 创建一个工程 选择芯片-STM32F103C8T6 填写工程信息 添加标准库到工程 标…

【数据结构与算法】图论及其相关算法

文章目录 图的基本介绍图的表示方式邻接矩阵邻接表 图的深度优先遍历(DFS)概述实现步骤代码实现 图的广度优先遍历(BFS)概述实现步骤代码实现 图的常用代码汇总最小生成树算法普里姆&#xff08;Prim&#xff09;算法算法实践 克鲁斯卡尔&#xff08;Kruskal&#xff09;算法并…

西南交通大学智能监测 培训课程练习2

2023.05.27培训 task1&#xff1a;spring、springboot task2&#xff1a;mybatis 目录 一、使用IDEA创建工程 1.1新建 ​编辑 1.2选择Maven 1.3创建包 二、添加相关依赖 2.1添加依赖 2.2更新Maven 三、配置application.yaml文件 四、相关注解的使用和理解 4.1Componen…

华硕天选3FX507ZC原装Windows11原厂预装系统工厂模式恢复安装带ASUSRecevory一键还原22H2版本

华硕天选3FX507ZC原装Windows11原厂预装系统工厂模式恢复安装带ASUSRecevory一键还原 文件地址&#xff1a;https://pan.baidu.com/s/1snKOsH3OMl3GZLqeAf-GLA?pwd8888 华硕工厂恢复系统 &#xff0c;安装结束后带隐藏分区以及机器所有驱动软件 需准备一个16G左右空u盘进行…