【从0开始学习Java | 第17篇】集合(中-Set部分)

在这里插入图片描述

文章目录

  • Java集合之Set:无序不重复的元素容器
    • 一、Set接口的核心特性
    • 二、常用实现类及底层原理
      • 1. HashSet:基于哈希表的高效实现
      • 2. LinkedHashSet:保留插入顺序的哈希实现
      • 3. TreeSet:基于红黑树的排序实现
    • 三、实现类对比与选择建议
    • 四、使用注意事项
    • 总结

Java集合之Set:无序不重复的元素容器

在Java集合框架中,Set是与List并列的重要接口,它以“无序且元素不重复”为核心特性,在数据去重、快速查找等场景中被广泛应用。本文将深入解析Set接口及其常用实现类,帮助你理解它们的底层原理与适用场景。

一、Set接口的核心特性

Set继承自Collection接口,但其行为与List有显著区别:

  • 无序性:元素的存储顺序与插入顺序无关(特殊实现类如LinkedHashSet除外),不能通过索引访问元素。
  • 唯一性/不重复:集合中不允许存在重复元素,判断重复的依据是equals()方法,若元素重写了equals(),通常需同时重写hashCode()以保证一致性。
  • 无索引:没有类似get(int index)的方法,遍历需通过迭代器(Iterator)或增强for循环。

Set接口的常用方法与Collection基本一致,如add()remove()contains()size()等,核心差异体现在实现类对“去重”和“查找效率”的不同优化上。

在这里插入图片描述

二、常用实现类及底层原理

1. HashSet:基于哈希表的高效实现

在这里插入图片描述

在这里插入图片描述1. JDK8以后,当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树
2. 如果集合中存储的是自定义对象,必须重写hashCodeequals方法才能用来比较属性值是否相同

在知道了HashSet的底层原理后,我们就可以回答以下三个问题了:

1. HashSet为什么存和取的顺序不一样?

因为HashSet可能是数组+链表+红黑树的结合体,而取的时候,是从最小的地址值开始遍历,而先放进去的元素的地址值不一定就小,以上都造成了HashSet的存取顺序不同。

2. HashSet为什么没有索引?

因为HashSet是数组+链表+红黑树的结合体,若存在索引,无法分配索引,难道同一个链表上和同一个红黑树上的元素都用同一个索引码,因此便取消了HashSet的索引。

3. HashSet是利用什么机制保证数据去重的?

首先利用 HashCode方法来确定元素的位置,再通过equals方法来判断属性值是否重复【重写了HashCode 和 equals 方法】。

HashSetSet最常用的实现类,底层通过哈希表(HashMap) 实现,其核心特性如下:

  • 存储原理:借助HashMapkey存储元素(value为固定常量),利用哈希表的特性保证元素唯一。
  • 去重逻辑:添加元素时,先通过hashCode()计算哈希值,若哈希值不同则直接存储;若哈希值相同,再通过equals()判断是否为同一元素,相同则拒绝添加。
  • 性能
    • add()、remove()、contains()等操作的平均时间复杂度为O(1),效率极高。
    • 无序性:元素存储位置由哈希值决定,与插入顺序无关。
  • 注意事项
    • 元素需重写hashCode()equals(),否则可能导致重复元素存入(如自定义对象未重写方法时,默认使用地址值判断【重要的事反复强调】)。
    • 线程不安全,多线程环境需手动同步(如使用Collections.synchronizedSet())。

示例代码

Set<String> hashSet = new HashSet<>();
hashSet.add("apple");
hashSet.add("banana");
hashSet.add("apple"); // 重复元素,添加失败
System.out.println(hashSet); // 输出可能为 [banana, apple](顺序不确定)

2. LinkedHashSet:保留插入顺序的哈希实现

在这里插入图片描述

LinkedHashSet继承自HashSet,底层通过LinkedHashMap实现,在哈希表基础上增加了双向链表,用于记录元素的插入顺序,因此具备以下特性:

  • 有序性:遍历元素时按插入顺序返回,解决了HashSet的无序问题。
  • 性能
    • 插入和删除效率略低于HashSet(因需维护链表),但遍历效率更高。
    • 时间复杂度仍为O(1)(平均),与HashSet接近。
  • 适用场景:需要去重且保留插入顺序的场景(如日志记录、历史操作跟踪)。

示例代码

Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("first");
linkedHashSet.add("second");
linkedHashSet.add("first"); // 去重
System.out.println(linkedHashSet); // 输出 [first, second](顺序与插入一致)

3. TreeSet:基于红黑树的排序实现

TreeSet底层通过红黑树(自平衡二叉查找树) 实现,核心特性是元素可排序

  • 两种排序方式
    • 自然排序:元素实现Comparable接口,重写compareTo()方法(如IntegerString默认支持)。

代码示例:

public class Student implements Comparable<Student> {private String name;private int age;@Overridepublic int compareTo(Student o) {//this:表示当前要添加的元素//o:表示已经在红黑树存在的元素return this.age - o.age; // 按年龄升序排列}
}

排序规则图示:

在这里插入图片描述

适用场景

  • 元素类有明确且唯一的默认排序逻辑(如数字按大小、字符串按字典序)。
  • 排序规则需要在多处复用,且不会频繁变更。
  • 元素类是自定义类,允许修改其代码以实现 Comparable 接口。

定制排序 / 比较器排序:创建TreeSet时传入Comparator对象,自定义排序逻辑。

// 按字符串长度排序
// lambda表达式版:
TreeSet<String> ts= new TreeSet<>((o1, o2) -> o1.length() - o2.length());// 原始版本:
TreeSet<String> ts= new TreeSet<>(new Comparator<String>() {@Override// o1:表示当前要添加的元素// o2:表示已经在红黑树存在的元素public int compare(String o1, String o2) {return o1.length() - o2.length();}
});

适用场景

  • 元素类无法修改(如 String、Integer 等 JDK 内置类),无法实现 Comparable
  • 需要临时改变排序规则(同一批元素在不同场景下按不同规则排序)。
  • 元素类已有默认排序,但需要覆盖默认规则(如数字默认升序,需临时按降序排列)。

  • 去重逻辑:通过排序规则判断元素是否重复(compareTo()返回0则视为重复,而非 equals() 方法,因为其底层为红黑树,非哈希表)。
  • 性能
    • add()、remove()、contains()操作的时间复杂度为O(log n),适合需要频繁排序和查找的场景。
    • 遍历元素时按排序顺序返回,无需额外排序操作。
  • 注意事项
    • 元素必须可比较(否则会抛出ClassCastException)。
    • 线程不安全,多线程环境需同步处理。

示例代码

// 自然排序(String默认按字典序)
Set<String> treeSet = new TreeSet<>();
treeSet.add("orange");
treeSet.add("apple");
treeSet.add("banana");
System.out.println(treeSet); // 输出 [apple, banana, orange](按字典序排序)// 定制排序(整数降序)
Set<Integer> customTreeSet = new TreeSet<>((a, b) -> b - a);
customTreeSet.add(3);
customTreeSet.add(1);
customTreeSet.add(2);
System.out.println(customTreeSet); // 输出 [3, 2, 1]

使用原则默认使用第一种,如果第一种不能满足当前需求,就使用第二种

三、实现类对比与选择建议

三者均不重复,且无索引哈希表 => 数组+单向链表+红黑树

实现类底层结构有序性去重依据平均时间复杂度适用场景
HashSet哈希表无序hashCode() + equals()O(1)高效去重,不关心顺序
LinkedHashSet哈希表+双向链表有序,插入顺序hashCode() + equals()O(1)去重且需保留插入顺序
TreeSet红黑树可排序compareTo()/ComparatorO(log n)去重且需排序或频繁范围查询

四、使用注意事项

  1. 元素的不可变性:若元素是可变对象,修改其属性可能导致哈希值或排序位置变化,引发Set内部状态混乱(如HashSet中元素修改后可能无法被正确查找)。建议使用不可变对象(如StringInteger)作为Set元素。

  2. hashCode()与equals()的一致性

    • a.equals(b) == true,则a.hashCode()必须等于b.hashCode()
    • 重写时尽量保证哈希值分布均匀(减少哈希冲突),避免影响HashSet性能。
  3. 线程安全:所有Set实现类均线程不安全,多线程并发修改时需使用Collections.synchronizedSet()包装,或直接使用ConcurrentHashMap(JDK 1.8+)的newKeySet()方法创建线程安全的Set

总结

HashSet追求高效,LinkedHashSet兼顾顺序,TreeSet专注排序。理解它们的底层原理(哈希表、红黑树)和特性差异,能帮助你在实际开发中选择最合适的容器,提升代码效率与可读性。


如果我的内容对你有帮助,请 点赞 评论 收藏 。创作不易,大家的支持就是我坚持下去的动力!
在这里插入图片描述

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

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

相关文章

玩转Docker | 使用Docker部署dufs文件管理工具

玩转Docker | 使用Docker部署dufs文件管理工具 前言 一、 dufs介绍 Dufs简介 核心特性 📁 静态文件服务 💾 文件夹打包下载 📤 拖拽上传文件/文件夹 ✏️ 文件在线创建、编辑与搜索 ⏳ 断点续传与部分传输 🔐 细粒度访问控制 🔒 HTTPS 安全传输 🌐 WebDAV 兼容支持…

【混合开发】vue+Android、iPhone、鸿蒙、win、macOS、Linux之android 把assert里的dist.zip 包解压到sd卡里

一图胜千言 上一篇有 <!-- 读写外部存储 --> <uses-permission android:name"android.permission.WRITE_EXTERNAL_STORAGE"android:maxSdkVersion"28"/> <uses-permission android:name"android.permission.READ_EXTERNAL_STORAGE&qu…

线程的创建.销毁

线程线程的创建在 C 中&#xff0c;线程的创建核心是通过std::thread类实现的&#xff0c;其构造函数需要传入一个可调用对象&#xff08;Callable Object&#xff09;作为线程入口。可调用对象包括普通函数、lambda 表达式、函数对象&#xff08;functor&#xff09;、类的成员…

MySQL基础全面解析

MySQL作为最流行的关系型数据库管理系统之一&#xff0c;是每一位开发者必备的核心技能。本文将系统性地解析MySQL的基础知识&#xff0c;结合关键概念与实战应用&#xff0c;帮助您构建扎实的数据库基础。1. SQL与NoSQL的本质区别SQL&#xff08;结构化查询语言&#xff09;数…

4、幽络源微服务项目实战:后端公共模块创建与引入多租户模块

前言 上节我们将电网巡检系统的前端vue2项目创建、配置&#xff0c;并构建了最基础的多租户界面&#xff0c;本节来继续构建后端的公共模块、多租户模块&#xff0c;并将公共模块引入到多租户模块中。 创建公共模块和多租户模块 在back父工程下创建两个Module&#xff0c;和…

STM32学习路线开启篇:芯片简介与课程简介

编写不易,请多多指教,觉得不错可以关注一下,相互学习 前言 一、课程配套资源 1、面包板 2、面包板专用的跳线 3、面包板的飞线 4、杜邦线 5、STM32F103C8T6最小系统板 6、0.96寸的OLED显示屏模块 7、电位器 8、按钮 9、LED灯 10、STLINK 11、USB转串口(TTL)模块 12、源蜂鸣器模…

图像直方图

图像直方图就是用来统计图像像素值分布的。灰度图分布读取灰度图phone cv2.imread(phone.png, cv2.IMREAD_GRAYSCALE) a phone.ravel() plt.hist(a, bins256) plt.show()如何可以获得当前像素值分布读取各通道的像素值分布img cv2.imread(phone.png) colors (b, g, r) for …

分类别柱状图(Vue3)

效果图&#xff1a;需求&#xff1a;男女年龄段占比<template><div class"go-ClassifyBar01"><v-chartref"vChartRef":option"option"style"width: 100%; height: 800px"></v-chart></div> </templa…

Apache Dubbo学习笔记-使用Dubbo发布、调用服务

Apache Dubbo经常作为一个RPC框架来使用&#xff0c;这篇文章主要介绍使用Dubbo配合注册中心来发布和调用服务。 Apache Dubbo和Spring Boot、JDK的版本对应关系。 Dubbo 分支最新版本JDKSpring Boot组件版本详细说明3.3.x (当前文档)3.3.08, 17, 212.x、3.x详情- 版本变更记录…

Python学习——字典和文件

前面python的学习中我们已经学习了python的函数和列表元组相关的内容&#xff0c;接下来我们来学习剩下的python语法&#xff1a;字典和文件 相关代码已经上传到作者的个人gitee&#xff1a;楼田莉子/Python 学习喜欢请点个赞谢谢 目录 字典 创建字典 查找key 新增/修改元素 …

swiper插件的使用

官方网址&#xff1a;https://www.swiper.com.cn/ 1、点击导航栏&#xff0c;获取Swiper里边的下载Swiper 2、选择要下载的版本【本次案例版本5.4.5】&#xff0c;然后解压缩文件夹&#xff0c;拿到swiper.min.js和swiper.min.css文件&#xff0c;放到项目对应的css文件和js文…

Vue3+JS 组合式 API 实战:从项目痛点到通用 Hook 封装

Vue3 组合式 API 的实战技巧 —— 组合式 API 帮我解决了不少 Options API 难以应对的问题&#xff0c;尤其是在代码复用和复杂组件维护上。一、为什么放弃 Options API&#xff1f;聊聊三年项目里的真实痛点​刚接触 Vue3 时&#xff0c;我曾因 “惯性” 继续用 Options API 写…

把 AI 塞进「电梯按钮」——基于 64 kB 零样本声纹的离线故障预测按钮

标签&#xff1a;零样本声纹、电梯按钮、离线 AI、TinyML、RISC-V、低功耗、GD32V303、故障预警 ---- 1. 背景&#xff1a;为什么按钮要「听声音」&#xff1f; 全国 700 万台电梯&#xff0c;按钮故障率 0.3 %/年&#xff0c;却常出现&#xff1a; • 机械卡滞、触点氧化&…

清华大学联合项目 论文解读 | MoTo赋能双臂机器人:实现零样本移动操作

研究背景 移动操作是机器人领域的核心挑战&#xff0c;它使机器人能够在各种任务和动态日常环境中为人类提供帮助。传统的移动操作方法由于缺乏大规模训练&#xff0c;往往难以在不同任务和环境中实现泛化。而现有操作基础模型虽在固定基座任务中表现出强泛化性&#xff0c;却无…

go webrtc - 2 webrtc重要概念

webrtc是一套音视频传输技术生态&#xff0c;不是一个协议或一个什么东西。3种模式本文基于 SFU 形式阐述&#xff01;重要概念&#xff1a;sfu 服务负责&#xff1a;信令 服务负责&#xff1a;peerConnection&#xff1a;track&#xff1a;房间&#xff1a;虚拟分组概念用户&a…

“下游任务”概念详解:从定义到应用场景

“下游任务”概念详解&#xff1a;从定义到应用场景 一、什么是“下游任务”&#xff1f; 在机器学习&#xff08;尤其是深度学习&#xff09;中&#xff0c;“下游任务”&#xff08;Downstream Task&#xff09;是相对“上游过程”而言的目标任务——可以理解为&#xff1a;我…

视频怎么做成 GIF?用 oCam 一键录制 GIF 动画超简单

GIF 动图因其生动直观、无需点击播放的特点&#xff0c;越来越受欢迎。你是否也曾看到一段有趣的视频&#xff0c;想把它做成 GIF 发给朋友或用在PPT里&#xff1f;其实&#xff0c;将视频片段转换为 GIF 并不需要复杂的视频剪辑技术&#xff0c;使用一款支持直接录制为 GIF 的…

Vue.config.js中的Webpack配置、优化及多页面应用开发

Vue.config.js中的Webpack配置、优化及多页面应用开发 在Vue CLI 3项目中&#xff0c;vue.config.js文件是工程化配置的核心入口&#xff0c;它通过集成Webpack配置、优化策略和多页面开发支持&#xff0c;为项目构建提供高度可定制化的解决方案。本文将从基础配置、性能优化、…

行业学习【电商】:直播电商的去头部化、矩阵号?

声明&#xff1a;以下部分内容含AI生成这两个词是当前直播电商和MCN领域的核心战略&#xff0c;理解了它们就理解了行业正在发生的深刻变化。一、如何理解“去头部化”&#xff1f;“去头部化” 指的是平台或MCN机构有意识地减少对超头部主播&#xff08;如曾经的李佳琦、薇娅&…

【MFC视图和窗口基础:文档/视图的“双胞胎”魔法 + 单文档程序】

大家好&#xff0c;我是你的MFC编程小伙伴&#xff01;学MFC就像探险古墓&#xff1a;到处是神秘的“房间”&#xff08;窗口&#xff09;和“宝藏”&#xff08;数据&#xff09;。今天咱们聊聊核心概念 – 视图、窗口和文档。这些是MFC的“骨架”&#xff0c;懂了它们&#x…