当前位置: 首页 > 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.xdnf.cn/news/1157707.html

相关文章:

  • leetcode75【经典动态规划】之:最长公共子序列
  • imx6ull-系统移植篇11——U-Boot 移植(下)
  • 【Java源码阅读系列57】深度解读Java MethodHandle 类源码
  • 神经网络:池化层
  • jQuery多库共存
  • SQL189 牛客直播各科目同时在线人数
  • c/c++-memory-management
  • 【PTA数据结构 | C语言版】是不是堆
  • SpringBoot集成Skywalking链路跟踪
  • 2025年渗透测试面试题总结-2025年HW(护网面试) 59(题目+回答)
  • 奥比中光双目摄像头实现物品抓取的机器人系统
  • 【Lua】多脚本引用
  • 数据结构 | 栈:构建高效数据处理的基石
  • Docker Compose
  • LeetCode 198 打家劫舍 LeetCode 213.打家劫舍II
  • Kotlin函数式接口
  • 力扣:动态规划java
  • kotlin Flow快速学习2025
  • 算法训练营DAY36 第九章 动态规划part04
  • Request和Response相关介绍
  • 数字图像处理(四:图像如果当作矩阵,那加减乘除处理了矩阵,那图像咋变):从LED冬奥会、奥运会及春晚等等大屏,到手机小屏,快来挖一挖里面都有什么
  • 《计算机网络》实验报告三 UDP协议分析
  • STM32-第八节-TIM定时器-4(编码器接口)
  • C++虚函数易错点整理
  • Python dataclass 高阶用法与技巧
  • springboot-profile
  • Direct3D 11学习(一)
  • 数学专业转行做大数据容易吗?需要补什么?
  • Web服务压力测试工具hey学习一:使用方法
  • 如何解决pip安装报错error subprocess-exited-with-error问题