臭名昭著aTrust隔离计划1.臭名昭著aTrust隔离计划2.VPN原理与aTrust隔离网络实践3.docker-easyconnect到底做了什么4.TUN(tunnel-隧道-虚拟网卡)模式
个人项目
personal(个人展示)GitHub Actions 自动部署个人展示项目
基于Quartz搭建的个人博客1.Quartz个人博客使用教程2.使用 rsync 增量部署 Quartz 博客3.使用 GitHub Actions 自动部署 Quartz 博客5.域名绑定6.CDN加速7.Google 搜索收录排查:为什么关键词搜得到别人,却搜不到我的网站?Obsidian、双向链接与知识图谱
架构设计
分布式分布式事务RPC原理
旅行
行程攻略青岛三日游行程攻略
前端
NginxNginx配置与反向代理入门
Nodenpm与npx的区别
数据库
mysql01.MySQL基础语法与约束02.MySQL数据库类型03.MySQL多表查询04.MySQL 索引05.MySQL 事务06.MySQL 隔离级别07.MySQL MVCC08.MySQL 锁09.EXPLAIN10.慢查询与 SQL 优化
mysql面试题01.索引问题02.事务与隔离级别03.锁机制04.存储引擎与性能优化05.用户权限、字符集与备份恢复
mysql实战01.EXPLAIN实战02.测试环境菜单误删排查与恢复03.MySQL IN 详解
网络
网络安全1.无线监听网卡苹果M系列虚拟机安装Kali Linux教程3.kali怎么使用RTL8812AU4.kali基础命令
网络基础进程之间怎样通过网络传递数据
网址常用网址
中间键
esElasticsearch 入门
mq
RabbitMQRabbitMQ高级RabbitMQ基础
1. MQ 基础2. MQ 核心角色与代码对应3. MQ Consumer Group 与消费模式4. SOFAMQ 账户变更代码实践5. MQ 可靠性问题6. 消息幂等
redis缓存击穿、穿透与雪崩Redis 分布式锁Redis 缓存问题Redis 数据结构
AI
使用说明
第三方插件&技能简介Archify使用与安装指南geekgeekRun使用OfferFlow使用指南Ponytail使用指南Skills Manager使用指南
CodexCodex CLI与IDE区别及使用指南Codex Hook单独配置与提交通知Codex MCP安装与使用指南Codex第三方插件安装与使用指南
Agent开发0-学习路线1.LLM、Token、上下文窗口与模型参数2.大模型 API、请求参数与响应结构3.Spring AI ChatClient4.Prompt、System Prompt、Prompt 模板6.结构化输出、JSON Schema7.SSE 流式响应8.会话 ID、聊天记录、Redis9.超时、重试、限流、降级10.*完成可运行聊天接口11.Function Calling、Tool Calling 原理12.用 Java 方法定义 Tool13.工具参数设计和校验14.实现订单查询工具15.实现物流查询工具16.实现库存查询工具17.多个工具自动选择18.工具异常、超时、重试
diy主机
主机零件学习1.CPU2.主板3.内存4.显卡5.硬盘6.散热7.风扇8.机箱热门型号价格表
捡垃圾diy-13600kf+5060diy-14600kf+5070
English高频词汇英文基础语法简单句子音标
GitGit常用命令Sublime Merge 使用指南
Java
工具库Caffeine本地缓存Reactor教程
工作实践如何使用@Async线程池使用与排查线上服务 CPU 使用率过高排查压测下RPC接口大量失败排查记录
基础1.HashMap2.ConcurrentHashMap3.HashSet4.final5.static6.volatile7.AtomicInteger8.String动态代理反射锁与线程同步线程注解
面试题1.HashMap 存储在方法内是线程安全的吗2.ArrayList参数传递与引用重新赋值3.== 和 equals 的区别、hashcode4.重载和重写的区别5.String、StringBuffer、StringBuilder区别及使用场景6.接口和抽象类7.ArrayList 与 LinkedList 的区别和扩容机制8.HashMap 和 Hashtable 的区别及底层实现9.Object 和 Objects 的区别10.ThreadLocal的原理和使用场景11.字节码是什么及作用12.Java类加载器有哪些13.双亲委派模型14.Java异常体系15.GC是什么16.GC如何判断对象可以被回收
面试总结0.HR面试模板1.面试一2.面试二3.面试三笔试
网络与 IO(黑马程序员-纯理论)01-网络编程基础02-IO模型与零拷贝03-BIO04-NIO基础与缓冲区05-NIO通道与选择器06-AIO
juc00-目录01-进程与线程02-线程同步与通信03-并发设计模式04-Java内存模型05-CAS与原子类06-ThreadLocal07-阻塞队列08-线程池使用与原理09-任务调度10-ForkJoin与享元模式11-AQS12-ReentrantLock13-读写锁14-并发协作工具15-ConcurrentHashMap16-CopyOnWrite与同步容器17-SkipListMap18-非阻塞队列
juc(工作常见)Java并发编程三个重点拆解
jvm1. JVM运行时数据区
Spring1.Spring IoC、DI与构造方法注入CGLIB、Byte Buddy 与 Spring AOPSpring 常用注解详解Spring Bean 生命周期
Spring MVC异常处理机制
linux常用命令
mac快捷键终端常用操作
python
基础01.Python是什么,为什么要安装Python02.Python项目中.venv是什么03.Python基础语法
基础实战sql替换
HTTPXHRMS员工详情接口调用(Python HTTPX)
windowswindows激活

面试主线

HashSet 的面试题抓住四点:元素不重复、底层是 HashMap、依靠 hashCode 和 equals 去重、顺序不保证。

一、💡 一句话理解

HashSet 是 Set 接口的常用实现,适合做去重和快速判断元素是否存在。

它没有索引,不保证遍历顺序,并且不是线程安全的。

二、🧭 理论:它是什么

2.1 核心特点

特点含义
元素不重复相等的元素只保留一份
没有索引不能像 List 一样通过下标取值
顺序不保证遍历顺序不一定等于插入顺序
允许 null最多保存一个 null
查询通常很快哈希分布合理时,add、remove、contains 通常是 O(1)
非线程安全多线程并发修改时不能直接共享使用

2.2 常见集合怎么选

需求选择
允许重复、需要下标ArrayList
只去重,不关心顺序HashSet
去重并保留插入顺序LinkedHashSet
去重并排序TreeSet

三、⚙️ 理论:它是怎么工作的

3.1 底层结构

HashSet 底层由 HashMap 支持。放入 HashSet 的元素,实际作为 HashMap 的 Key 保存;Value 只是内部占位对象。

HashSet.add(element)
        ↓
HashMap.put(element, 占位对象)
        ↓
利用 Key 不重复,实现元素去重

底层细节可结合 1.HashMap 一起复习。

3.2 怎样判断元素重复

添加元素时,可以简化为两步:

  1. 调用 hashCode() 定位元素可能所在的桶。
  2. 再用 equals() 判断桶中是否已有相等元素。
Set<String> names = new HashSet<>();
 
System.out.println(names.add("张三")); // true:添加成功
System.out.println(names.add("张三")); // false:已存在,集合不变

必须记住的约定

两个对象如果 equals() 相等,hashCode() 必须相同;但 hashCode() 相同,两个对象不一定相等,因为可能发生哈希冲突。

3.3 自定义对象为什么可能去重失败

如果不重写 equals() 和 hashCode(),两个字段内容相同的普通对象仍可能被当成不同对象。

下面规定“用户 ID 相同就是同一个用户”:

import java.util.Objects;
 
final class User {
    private final int id;
    private final String name;
 
    User(int id, String name) {
        this.id = id;
        this.name = name;
    }
 
    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (!(object instanceof User other)) {
            return false;
        }
        return id == other.id;
    }
 
    @Override
    public int hashCode() {
        // 必须使用与 equals 相同的身份字段。
        return Objects.hash(id);
    }
}

对象加入 HashSet 后,不要修改参与 equals() 和 hashCode() 的字段,否则 contains()、remove() 可能找不到它。上例把 id 设为 final,就是为了避免这个问题。

四、🚀 实践:常用写法

4.1 前置准备

只需要 JDK 自带的 java.util.HashSet,不需要第三方依赖。

4.2 可以拿来干什么

  • 把 List 中的重复数据去掉。
  • 用 contains() 快速判断元素是否存在。
  • 利用 add() 的返回值判断元素是否第一次出现。

4.3 完整实践:去重与判断重复

新建 HashSetDemo.java:

import java.util.HashSet;
import java.util.List;
import java.util.Set;
 
public class HashSetDemo {
    public static void main(String[] args) {
        // 把允许重复的 List 转为 Set,完成去重。
        List<String> tags = List.of("Java", "Redis", "Java");
        Set<String> uniqueTags = new HashSet<>(tags);
 
        System.out.println(uniqueTags.size());            // 2
        System.out.println(uniqueTags.contains("Java")); // true
 
        // add 的返回值可直接判断元素是否第一次出现。
        System.out.println(uniqueTags.add("MySQL")); // true
        System.out.println(uniqueTags.add("MySQL")); // false
    }
}

不要断言 uniqueTags 的打印顺序,因为 HashSet 没有顺序保证。

编译并运行:

javac HashSetDemo.java
java HashSetDemo

预期输出中的四个值依次为 2、true、true、false。

五、🎤 面试时怎么流利地说

5.1 回答路线

按这条线回答即可:

是什么 → 有什么特点 → 底层怎么实现 → 怎样判断重复 → 使用边界

5.2 30 秒标准回答

HashSet 是 Set 接口的常用实现,主要用于元素去重和快速判断元素是否存在。它底层使用 HashMap,元素作为 Key 保存。添加元素时,先根据 hashCode() 定位桶,再通过 equals() 判断是否重复。哈希分布合理时,添加、删除和查询通常是 O(1)。它不保证遍历顺序,也不是线程安全的;自定义对象作为元素时,需要正确重写 equals() 和 hashCode()。

5.3 1—2 分钟展开回答

HashSet 的特点是元素不重复、没有索引、遍历顺序不保证,并且允许一个 null。它底层由 HashMap 支持,放入集合的元素会作为 Map 的 Key。

调用 add() 时,会先通过元素的 hashCode() 定位桶。如果桶中存在候选元素,再用 equals() 判断是否真的相等。若已存在相等元素,集合保持不变,add() 返回 false;否则保存元素并返回 true。因此,自定义对象要用同一组业务身份字段实现 equals() 和 hashCode()。

哈希分布合理时,add()、remove() 和 contains() 通常是 O(1)。使用时不能依赖它的遍历顺序,也不要在元素加入后修改参与相等判断的字段。如果需要保留插入顺序,我会使用 LinkedHashSet;需要排序则使用 TreeSet;并发修改场景则选择合适的同步或并发方案。

5.4 高频追问与短答

1. 为什么同时重写 equals() 和 hashCode()?

hashCode() 用于定位桶,equals() 用于确认对象是否相等。只重写其中一个会破坏两者约定,可能导致逻辑上重复的对象无法去重。

2. hashCode() 相同,对象一定相等吗?

不一定。不同对象可能产生相同哈希值,这就是哈希冲突,还要继续用 equals() 判断。

3. HashSet 和 HashMap 有什么关系?

HashSet 底层使用 HashMap。Set 中的元素作为 Map 的 Key,利用 Key 的唯一性实现去重。

4. HashSet 为什么不能保证顺序?

元素的位置由哈希结果和内部容量决定,扩容后位置还可能变化。如果需要插入顺序,使用 LinkedHashSet。

5. HashSet 是线程安全的吗?

不是。并发修改时可以进行外部同步,或根据具体读写特点选择并发集合,不能把 fail-fast 异常当成线程安全保证。

六、📌 总结

  • 用途:去重、快速判断是否存在。
  • 结构:底层是 HashMap,元素作为 Key。
  • 去重:先用 hashCode() 定位,再用 equals() 确认。
  • 边界:不保证顺序、非线程安全、不要修改哈希关键字段。
  • 选型:保序用 LinkedHashSet,排序用 TreeSet。

记忆句

HashSet:底层 Map 存 Key,hash 定位,equals 判重;顺序不保,并发不安全。

七、📚 官方资料

本文依据 Java SE 25 API 整理,查阅日期:2026-10-12。Oracle Collections 教程页面基于 JDK 8,仅用于集合选型顺序参考。