当前位置: 首页 > news >正文

050_Set接口(HashSet / TreeSet / LinkedHashSet)

一、Set 接口概述

Set是 Java 集合框架中无序、不可重复的集合接口,继承自Collection接口。其核心特性是:

  • 不可重复性:集合中不允许存储重复元素(通过equals()方法判断是否重复)。
  • 无序性:元素存储顺序与插入顺序无关(TreeSet和LinkedHashSet除外,有特殊有序性)。
  • 允许存储null元素,但最多只能有一个null(因不可重复)。

Set接口没有提供额外的方法,所有方法均继承自Collection,主要实现类包括HashSet、TreeSet和LinkedHashSet,三者在底层结构、有序性和性能上差异显著。

在这里插入图片描述

二、HashSet 详解

2.1 底层结构与核心原理

HashSet是Set接口最常用的实现类,底层基于哈希表(HashMap) 实现(JDK 8 + 中哈希表为 “数组 + 链表 / 红黑树” 结构)。其核心原理是:

  • 元素存储在HashMap的key位置(value为固定常量),利用HashMap的key不可重复特性实现去重。
  • 通过元素的hashCode()方法计算哈希值,确定元素在哈希表中的存储位置;通过equals()方法判断元素是否重复。

2.2 核心特点

特点说明
无序性元素存储顺序与插入顺序无关(哈希值决定存储位置,不保证顺序)
不可重复性依赖hashCode()和equals()方法去重:若两元素hashCode()相等且equals()返回true,则视为重复
性能优异新增(add())、查询(contains())、删除(remove())操作的平均时间复杂度为O(1)
允许 null最多存储一个null元素(因不可重复)
线程不安全非同步实现,多线程并发修改可能导致数据异常

2.3 去重原理与关键方法重写

HashSet的去重逻辑依赖元素的hashCode()和equals()方法,必须同时重写这两个方法才能保证去重正确性:

  • 第一步:计算元素的hashCode(),确定在哈希表中的存储桶位置。
  • 第二步:若桶中已有元素,通过equals()方法逐一比较,若返回true则视为重复,不存储;否则存入桶中(哈希冲突时以链表或红黑树形式存储)。

示例:自定义对象去重

import java.util.HashSet;
import java.util.Objects;// 自定义类(需重写hashCode和equals)
class Student {private String id;private String name;public Student(String id, String name) {this.id = id;this.name = name;}// 重写hashCode:以id作为哈希计算依据(id相同则哈希值相同)@Overridepublic int hashCode() {return Objects.hash(id);}// 重写equals:id相同则视为同一对象@Overridepublic boolean equals(Object obj) {if (this == obj) return true;if (obj == null || getClass() != obj.getClass()) return false;Student student = (Student) obj;return Objects.equals(id, student.id);}
}public class HashSetDemo {public static void main(String[] args) {HashSet<Student> set = new HashSet<>();set.add(new Student("001", "张三"));set.add(new Student("001", "李四")); // id重复,视为重复元素,不存储set.add(new Student("002", "王五"));System.out.println(set.size()); // 输出:2(去重后保留2个元素)}
}

在这里插入图片描述

2.4 适用场景

  • 需快速去重且对元素顺序无要求的场景(如存储用户 ID、标签集合)。
  • 新增、查询、删除操作频繁的场景(依赖哈希表的高效性能)。

三、LinkedHashSet 详解

3.1 底层结构与核心原理

LinkedHashSet是HashSet的子类,底层基于哈希表(HashMap)+ 双向链表实现。其核心改进是:

  • 继承HashSet的哈希表结构,保证去重和查询性能。
  • 通过双向链表维护元素的插入顺序,使集合迭代时按插入顺序返回元素。
    链表的每个节点记录前一个和后一个元素的引用,因此迭代遍历速度比HashSet更快(无需遍历哈希表中的空桶)。

3.2 核心特点

特点说明
有序性元素按插入顺序存储和迭代(链表维护顺序,哈希表保证去重)
不可重复性同HashSet,依赖hashCode()和equals()方法去重
性能新增、删除性能略低于HashSet(需额外维护链表指针),迭代性能更高
内存开销比HashSet大(需存储链表节点的前后引用)
线程不安全同HashSet,非同步实现

3.3 与 HashSet 的对比

维度HashSetLinkedHashSet
顺序无序(哈希值决定位置)有序(插入顺序,链表维护)
迭代速度较慢(可能遍历空桶)较快(链表顺序迭代,无空桶)
内存占用较低(仅哈希表)较高(哈希表 + 链表)
适用场景无顺序要求的去重需保留插入顺序的去重

在这里插入图片描述

3.4 适用场景

  • 需去重且需保留元素插入顺序的场景(如日志记录、历史操作轨迹)。
  • 频繁迭代遍历的场景(链表迭代效率更高)。

四、TreeSet 详解

4.1 底层结构与核心原理

TreeSet是Set接口中唯一可排序的实现类,底层基于红黑树(TreeMap) 实现。其核心特性是:

  • 元素存储在TreeMap的key位置,利用红黑树的自平衡排序特性保证元素有序。
  • 元素需按指定规则排序(自然排序或定制排序),不允许存储无法比较的元素。

4.2 排序方式

  1. 自然排序(默认)
    元素所属类实现Comparable接口,重写compareTo(T o)方法定义排序规则:
class User implements Comparable<User> {private String name;private int age;// 按年龄升序排序,年龄相同则按姓名排序@Overridepublic int compareTo(User o) {if (this.age != o.age) {return this.age - o.age; // 年龄升序}return this.name.compareTo(o.name); // 姓名字典序}
}
  1. 定制排序(外部比较器)
    创建TreeSet时传入Comparator接口实现类,定义排序规则(无需元素类实现Comparable):
// 按年龄降序排序的比较器
Comparator<User> ageDescComparator = (u1, u2) -> u2.age - u1.age;// 使用定制排序创建TreeSet
Set<User> treeSet = new TreeSet<>(ageDescComparator);

4.3 核心特点

特点说明
有序性元素按排序规则存储(非插入顺序),默认升序,可通过比较器定制
不可重复性通过比较器结果去重:若compareTo()或compare()返回0,视为重复元素
性能新增、删除、查询时间复杂度为O(log n)(红黑树平衡操作开销)
无 null 元素不允许存储null(排序时无法比较null,会抛出NullPointerException)
线程不安全非同步实现,多线程需额外同步处理

在这里插入图片描述

4.4 适用场景

  • 需对元素排序的场景(如排行榜、成绩排序、区间查询)。
  • 需按自定义规则去重和排序的场景(如按对象属性去重 + 排序)。

五、HashSet、TreeSet、LinkedHashSet 对比

维度HashSetTreeSetLinkedHashSet
底层结构哈希表(数组 + 链表 / 红黑树)红黑树哈希表 + 双向链表
有序性无序(哈希值决定)有序(排序规则)有序(插入顺序)
去重依据hashCode()+equals()compareTo()/compare()hashCode()+equals()
新增 / 删除性能最高(O(1))中(O(log n))中(略低于 HashSet)
迭代性能中(可能遍历空桶)中(红黑树中序遍历)最高(链表顺序迭代)
是否允许 null允许(最多 1 个)不允许允许(最多 1 个)
典型应用场景快速去重,无序需求排序场景,区间查询去重 + 保留插入顺序

六、使用注意事项

  1. 重写hashCode()和equals()的规范(针对 HashSet/LinkedHashSet)
    • 若两元素equals()返回true,则hashCode()必须相等(否则哈希表会视为不同元素,导致重复存储)。
    • 若两元素hashCode()相等,equals()可返回false(哈希冲突,通过链表 / 红黑树存储)。
  2. TreeSet 的排序一致性
    • 排序规则(compareTo()/compare())需与equals()保持一致:若compareTo()返回0,则equals()应返回true(否则会出现 “逻辑重复但未去重” 的矛盾)。
  3. 线程安全性
    • 三者均为线程不安全集合,多线程并发修改需通过Collections.synchronizedSet()包装,或使用ConcurrentHashMap(JDK 8+)的相关实现。
  4. 性能优化
    • HashSet初始化时指定容量(避免频繁扩容)。
    • 避免在TreeSet中存储无法比较的元素(会抛出ClassCastException)。

七、总结

Set接口的三大实现类各有侧重,选择时需结合顺序需求、性能需求和去重规则

  • HashSet:优先选择,适用于无序、快速去重场景,性能最优。
  • LinkedHashSet:需保留插入顺序时使用,迭代性能优于 HashSet,内存开销略高。
  • TreeSet:需排序或区间查询时使用,依赖比较器实现有序性和去重。

理解三者的底层结构(哈希表 / 红黑树 / 链表)是掌握其特性的核心,合理选择可显著提升集合操作
的效率和代码可读性。

http://www.dtcms.com/a/289806.html

相关文章:

  • 力扣面试150题--搜索插入位置
  • 某市公安局视频图像信息综合应用平台设计方案Word(446页)
  • AI产品经理面试宝典第40天:用户反馈处理与技术应用面试题与答法
  • 多校2+多校1的遗珠
  • 信道相关系数
  • 安装kali时出现“安装步骤失败“如何解决及后续软件安装
  • Python自动化测试项目实战
  • QT项目-仿QQ音乐的音乐播放器(第一节)
  • 什么是卡贴???
  • 国产电钢琴性价比实战选购指南
  • Python 虚拟环境与构建工具全景指南:功能介绍与对比分析
  • 基于Transformer的心理健康对话系统:从零构建AI心理咨询助手
  • 【全球甲烷估算模型】简化一箱模型(1-box model)
  • MySQL中的排序和分页
  • [simdjson] 实现不同CPU调度 | 自动硬件适配的抽象
  • C 语言经典编程题实战:从基础算法到趣味问题全解析
  • MybatisPlus-09.核心功能-IService开发复杂业务接口
  • 论文阅读:BLIP-2 (2023.4)
  • KOSMOS-2: 将多模态大型语言模型与世界对接
  • 第一章: 初识 Redis:背后的特性和典型应用场景
  • 你的created_time字段,用DATETIME还是TIMESTAMP?
  • brpc的安装与使用介绍以及channel的封装
  • spring-ai-alibaba 迭代字符分割器
  • RPG61.制作敌人攻击波数一
  • 30天打牢数模基础-AdaBoost讲解
  • CICS Application Programming Fundamentals 第8-6章
  • arinc818_icd设计范例
  • LLVM中AST节点类型
  • RGB颜色值如何转到灰度值
  • [每日随题14] 递推 - 滑动窗口 - 数学