选HashSet还是TreeSet取决于是否需要排序:HashSet基于HashMap,依赖hashCode和equals,增删查平均O(1),允许一个null;TreeSet基于红黑树,依赖compareTo或Comparator,操作O(log n),支持范围查询但默认不允null。

选 HashSet 还是 TreeSet,关键看你要不要“顺序”和“排序能力”,而不是只看“能不能去重”。两者底层结构完全不同,行为差异是本质性的。
底层结构决定一切
HashSet 实际上是 HashMap 的包装:它把每个元素当 key 存进 HashMap,value 固定用一个空对象(PRESENT)。所以它的所有操作都依赖 hashCode() 和 equals() —— 元素是否重复,全靠这两个方法配合哈希桶定位来判断。
TreeSet 底层是 TreeMap,也就是红黑树。它不看哈希值,而是靠 compareTo() 或 compare() 方法做大小比较,每次插入都动态调整树结构,天然维持有序。
- HashSet 不关心元素怎么比大小,只关心“是不是同一个东西”
- TreeSet 不关心哈希值,只关心“这个东西在序列里该排第几”
性能表现截然不同
HashSet 的 add、remove、contains 平均都是 O(1),适合高频判重场景,比如接口幂等校验、IP 黑白名单、消息去重。数据量越大,优势越明显。
立即学习“Java免费学习笔记(深入)”;
将后端 Java 接口文档转换为前端 TypeScript 类型定义(interface / type)。输入:飞书文档、Spring Boot 接口文档、Swagger/OpenAPI、手写 API 文档等。输出:包含请求参数与响应结果的 .md 文件,含完整 JSDoc 注释。触发关键词:Java 转 TS、接口转换、生成前端类型、接口文档转 TypeScript、DTO/VO/BO 转 interface、后端文档转前端类型、生成接口类型、类型定义生成。
TreeSet 所有核心操作都是 O(log n),稳定但慢一截。不过它能快速拿到最小值(first())、最大值(last()),还能做 subSet、headSet、tailSet 这类范围查询——HashSet 做不了这些。
- 要毫秒级判断“这个订单号处理过了吗?” → 用 HashSet
- 要实时查“今天成交额前 10 的用户是谁?”或“分数在 85–95 之间的学生有哪些?” → 用 TreeSet
null 值处理方式不同
HashSet 允许存一个 null,因为 HashMap 的 key 可以为 null。
TreeSet 不允许 null(除非你显式传入能处理 null 的 Comparator,但默认构造器下会直接抛 NullPointerException)。这是因为红黑树在比较时无法对 null 调用 compareTo。
- 业务数据可能含 null?优先避开 TreeSet,或提前过滤
- 需要排序又可能有 null?考虑先 map 转换(如 null → Integer.MIN_VALUE),或改用其他结构(如排序后用 List + Collections.binarySearch)
自定义对象使用必须注意
往 HashSet 放自定义对象,必须同时重写 hashCode() 和 equals(),否则去重失效——这是最常见线上 bug 来源之一。
往 TreeSet 放自定义对象,要么让类实现 Comparable 并重写 compareTo,要么创建 TreeSet 时传入 Comparator。如果没做,运行时 add 就抛 ClassCastException。
- 只重 equals 不重 hashCode → HashSet 认为两个对象不同,实际却是同一语义
- 没实现 Comparable 也没传 Comparator → TreeSet 运行时报错,不是编译报错

















