Java集合HashSet与TreeSet

聚焦Set集合两大核心实现类HashSetTreeSet,梳理核心特性、底层数据结构、实战用法及避坑要点。

一、Set集合基础特性

Set是Java单列集合Collection的子接口,全系列通用三大核心特性

  1. 不重复:集合中无法存储相同元素
  2. 无索引:不支持通过下标获取元素,无法普通for循环遍历
  3. 无独有方法:常用API全部继承自Collection接口

本文仅讲解最常用的两个实现类:HashSet(无序去重)、TreeSet(排序去重)。


二、HashSet 详解

2.1 核心特点

  • 无序:元素添加顺序 ≠ 遍历输出顺序
  • 不重复、无索引(Set通用特性)
  • 增删改查性能优异,是日常开发最常用的Set实现类

2.2 底层原理:哈希表

HashSet的高性能源于哈希表数据结构,JDK版本不同结构有差异:

  • JDK 8之前:数组 + 单向链表
  • JDK 8及以后:数组 + 链表 + 红黑树(优化查询性能)

关键概念:哈希值

  1. 所有Java对象均可调用Object.hashCode()获取int类型哈希码
  2. 特性:同一对象多次调用返回值相同;不同对象可能哈希值相同(哈希碰撞)

元素存储完整流程

  1. 初始化:创建默认长度16、加载因子0.75的数组table
  2. 计算位置:通过元素哈希值 % 数组长度算出存储下标
  3. 空位直接存:下标位置为null,直接存入元素
  4. 碰撞去重:位置非空时,调用equals()比较元素内容

    • 内容相同:判定为重复元素,不存储
    • 内容不同:形成链表,JDK8后新元素挂在链表尾部
  5. 树化优化:链表长度>8 且 数组长度≥64,链表自动转为红黑树
  6. 扩容机制:数组元素达到 16*0.75=12 时,自动扩容为原容量2倍

2.3 自定义对象去重(重点)

HashSet默认仅能对String、Integer等基础类型去重;自定义对象必须重写两个方法才能实现内容去重:

  1. hashCode():保证相同内容对象哈希值一致
  2. equals():判断对象内容是否相同

实战代码示例

  1. 实体类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 + "'}";
     }
    }
  2. 测试类: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底层完全基于红黑树(自平衡二叉排序树),特性:

  1. 自动排序,增删改查性能稳定
  2. 去重规则:排序比较结果为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 选型指南

特性HashSetTreeSet
底层结构哈希表(数组+链表+红黑树)红黑树
顺序无序可排序(默认升序)
性能增删查最优(O(1))排序场景最优(O(logn))
去重依据hashCode()+equals()比较器返回值为0
适用场景通用去重、无排序需求元素排序、范围查询

五、学习总结

  1. HashSet:Java开发首选Set集合,核心是哈希表,自定义对象必须重写hashCode()equals()实现去重;
  2. TreeSet:专注排序场景,核心是红黑树,自定义对象需通过ComparableComparator指定排序规则;
  3. 通用原则:仅去重选HashSet,需要排序选TreeSet

标签: none

添加新评论