Java集合HashSet与TreeSet核心用法与底层原理
Java集合HashSet与TreeSet
聚焦Set集合两大核心实现类HashSet与TreeSet,梳理核心特性、底层数据结构、实战用法及避坑要点。
一、Set集合基础特性
Set是Java单列集合Collection的子接口,全系列通用三大核心特性:
- 不重复:集合中无法存储相同元素
- 无索引:不支持通过下标获取元素,无法普通for循环遍历
- 无独有方法:常用API全部继承自
Collection接口
本文仅讲解最常用的两个实现类:HashSet(无序去重)、TreeSet(排序去重)。
二、HashSet 详解
2.1 核心特点
- 无序:元素添加顺序 ≠ 遍历输出顺序
- 不重复、无索引(Set通用特性)
- 增删改查性能优异,是日常开发最常用的Set实现类
2.2 底层原理:哈希表
HashSet的高性能源于哈希表数据结构,JDK版本不同结构有差异:
- JDK 8之前:数组 + 单向链表
- JDK 8及以后:数组 + 链表 + 红黑树(优化查询性能)
关键概念:哈希值
- 所有Java对象均可调用
Object.hashCode()获取int类型哈希码 - 特性:同一对象多次调用返回值相同;不同对象可能哈希值相同(哈希碰撞)
元素存储完整流程
- 初始化:创建默认长度16、加载因子0.75的数组
table - 计算位置:通过
元素哈希值 % 数组长度算出存储下标 - 空位直接存:下标位置为
null,直接存入元素 碰撞去重:位置非空时,调用
equals()比较元素内容- 内容相同:判定为重复元素,不存储
- 内容不同:形成链表,JDK8后新元素挂在链表尾部
- 树化优化:链表长度>8 且 数组长度≥64,链表自动转为红黑树
- 扩容机制:数组元素达到
16*0.75=12时,自动扩容为原容量2倍
2.3 自定义对象去重(重点)
HashSet默认仅能对String、Integer等基础类型去重;自定义对象必须重写两个方法才能实现内容去重:
hashCode():保证相同内容对象哈希值一致equals():判断对象内容是否相同
实战代码示例
实体类
Student(重写核心方法)import java.util.Objects; public class Student { private String name; private int age; private String address; private String phone; // 无参、全参构造 public Student() {} public Student(String name, int age, String address, String phone) { this.name = name; this.age = age; this.address = address; this.phone = phone; } // getter/setter省略 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } // 重写equals:比较对象内容 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && Objects.equals(name, student.name) && Objects.equals(address, student.address) && Objects.equals(phone, student.phone); } // 重写hashCode:根据内容生成哈希值 @Override public int hashCode() { return Objects.hash(name, age, address, phone); } // 重写toString:格式化输出 @Override public String toString() { return "Student{name='" + name + "', age=" + age + " , address='" + address + "', phone='" + phone + "'}"; } }测试类:HashSet去重效果
import java.util.HashSet; import java.util.Set; public class HashSetTest { public static void main(String[] args) { Set<Student> studentSet = new HashSet<>(); // 创建两个内容完全相同的对象 Student s1 = new Student("张三", 18, "北京", "123456"); Student s2 = new Student("张三", 18, "北京", "123456"); studentSet.add(s1); studentSet.add(s2); // 输出仅1个元素,去重成功 System.out.println(studentSet); } }
三、TreeSet 详解
3.1 核心特点
- 可排序:默认对元素升序排列
- 不重复、无索引(Set通用特性)
- 排序基于红黑树实现,兼顾排序与性能
3.2 底层原理:红黑树
TreeSet底层完全基于红黑树(自平衡二叉排序树),特性:
- 自动排序,增删改查性能稳定
- 去重规则:排序比较结果为0 即判定为重复元素
3.3 默认排序规则
- 数值类型(Integer/Double):按数值大小升序
- 字符串类型:按字符ASCII码值升序
- 自定义对象:无法直接排序,必须手动指定排序规则
3.4 自定义对象排序(两种方案)
TreeSet支持两种排序方式,比较器优先级 > 类自带排序
方案1:实体类实现Comparable接口
实体类重写compareTo()方法,定义默认排序规则
// 教师实体类:实现Comparable接口
public class Teacher implements Comparable<Teacher> {
private String name;
private int age;
private double salary;
// 无参、全参构造
public Teacher() {}
public Teacher(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
// getter/setter省略
public String getName() { return name; }
public int getAge() { return age; }
public double getSalary() { return salary; }
// 核心:定义排序规则(按年龄升序)
@Override
public int compareTo(Teacher o) {
// 正数:当前对象大;负数:当前对象小;0:重复
return this.age - o.age;
}
@Override
public String toString() {
return "Teacher{name='" + name + "', age=" + age + ", salary=" + salary + "}";
}
}方案2:创建TreeSet时传入Comparator比较器
匿名内部类/Lambda表达式,灵活定义临时排序规则
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetTest {
public static void main(String[] args) {
// 方式1:使用类默认排序(年龄升序)
Set<Teacher> teacherSet1 = new TreeSet<>();
// 方式2:自定义比较器(薪资降序,Lambda简化)
Set<Teacher> teacherSet2 = new TreeSet<>((o1, o2) -> Double.compare(o2.getSalary(), o1.getSalary()));
// 添加元素
teacherSet2.add(new Teacher("老陈", 20, 6232.4));
teacherSet2.add(new Teacher("dlei", 18, 3999.5));
teacherSet2.add(new Teacher("老王", 22, 9999.9));
// 按薪资降序输出
System.out.println(teacherSet2);
}
}四、HashSet vs TreeSet 选型指南
| 特性 | HashSet | TreeSet |
|---|---|---|
| 底层结构 | 哈希表(数组+链表+红黑树) | 红黑树 |
| 顺序 | 无序 | 可排序(默认升序) |
| 性能 | 增删查最优(O(1)) | 排序场景最优(O(logn)) |
| 去重依据 | hashCode()+equals() | 比较器返回值为0 |
| 适用场景 | 通用去重、无排序需求 | 元素排序、范围查询 |
五、学习总结
- HashSet:Java开发首选Set集合,核心是哈希表,自定义对象必须重写
hashCode()和equals()实现去重; - TreeSet:专注排序场景,核心是红黑树,自定义对象需通过
Comparable或Comparator指定排序规则; - 通用原则:仅去重选HashSet,需要排序选TreeSet。