TreeSet存储对象如何判断重复

本文探讨了在Java中使用TreeSet存储自定义对象时所需做的改动,特别是当涉及到自定义比较器时。通过示例,解释了如何通过重写Comparator的compare方法来确保对象的独特性,并指出在TreeSet中,add操作依赖于Comparable或Comparator接口来确定元素的唯一性。

TreeSet存储自定义对象

TreeSet存储自定义对象需要做哪些改动

我们知道,在HashSet里面存储自定义对象时要重写HashCode()和equals()方法。同样的在TreeSet里面存储自定义对象时也要做一些改动。

以自定义比较器为例

public class User {
        private String name;
        private int age
        }
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    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;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
     }
}
public static void main(String[] args) {
        TreeSet<User> users = new TreeSet<>(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getAge()-o2.getAge();
            }
        });
        User user01 = new User("张三", 20);
        User user02 = new User("张三", 30);
        User user03 = new User("王五", 4);
        User user04 = new User("李四", 50);
        User user05 = new User("张三", 20);
        User user06 = new User("赵六", 20);
        users.add(user01);
        users.add(user02);
        users.add(user03);
        users.add(user04);
        users.add(user05);
        users.add(user06);
        users.forEach(System.out::println);

    }

提问:TreeSet里面存储有几个对象?
实际运行得知里面有4个对象。
在这里插入图片描述
可以看到age为20的对象只有一个。接下来对匿名内部类的代码进行修改

public int compare(User o1, User o2) {
                if (o1.getAge()== o2.getAge()){
                    return o1.getName().compareTo(o2.getName());
                }
                return o1.getAge()-o2.getAge();
            }

重新运行
在这里插入图片描述
以上问题是我在自学java过程中遇到的。当时我很疑惑,问同学建议我重写HashCode()和equals()。我带着疑惑重写后发现结果并没有不同。思考之后我想到TreeSet的底层是排序二叉树,排序前元素比大小,里面也不牵扯哈希值。难道Comparator匿名内部类的代码直接影响了二叉树的存储?对比前后代码我发现只比较年龄大小的话年龄相同程序认为是同一元素,Set中元素不能重复,所以相同年龄的对象只能在二叉树上面存一个。加上name的比较这样就能合理确认是否是同一个元素。

总的来说

TreeSet集合中add()底层主要依赖实现comparable和comparator接口。
对于非自定义对象(具体可比性的对象),因为hashset和treeset已经实现了比较,不要重新方法,而对于自定义对象(不具有可比性的对象),使用hashset使用需要重写hashcode和equals方法,而使用TreeSet集合需要实现comparator或者comparable重写compare()或者compareTo()方法,实现比较。

<think>我们正在讨论HashSet和TreeSet存储方式。根据引用内容: - HashSet基于哈希表(HashMap)实现,使用哈希算法存储元素(引用[1][2][3])。 - TreeSet基于红黑树(一种自平衡二叉查找树)实现,元素按顺序存储(引用[1][4])。 下面我将详细解释两者的存储机制: ### 1. **HashSet的存储方式** - **底层数据结构**:哈希表(实际是HashMap实例)[^1][^2]。 - **存储原理**: 1. 添加元素时,调用元素的`hashCode()`方法计算哈希值。 2. 根据哈希值决定元素在哈希表中的存储位置(桶位置)。 3. 如果多个元素的哈希值相同(哈希冲突),则通过链表或红黑树解决(Java 8+优化)[^3]。 - **去机制**:同时依赖`hashCode()`和`equals()`方法: - 如果两个元素的`hashCode()`返回值不同,直接存入不同位置。 - 如果`hashCode()`相同,再调用`equals()`比较: - `equals()`返回`true` → 视为重复元素,拒绝添加。 - `equals()`返回`false` → 存入同一桶位的链表/树中[^3]。 - **示例**: ```java Set<String> set = new HashSet<>(); set.add("A"); // 计算"A"的哈希值,存入对应桶位 set.add("A"); // 哈希值相同且equals()为true → 去 ``` ### 2. **TreeSet存储方式** - **底层数据结构**:红黑树(自平衡二叉搜索树)[^1][^4]。 - **存储原理**: 1. 添加元素时,通过比较器(`Comparator`)或元素的自然顺序(实现`Comparable`接口)确定节点位置。 2. 从根节点开始递归比较: - 若当前节点为空,则插入新节点。 - 若元素小于当前节点,转向左子树。 - 若元素大于当前节点,转向右子树。 - 若比较结果为0(相等),视为重复,拒绝插入[^4]。 3. 插入后通过旋转和变色维持红黑树的平衡性。 - **去机制**:完全依赖比较结果: - 若`compareTo()`或`compare()`返回0,视为重复元素[^4]。 - 与`equals()`方法无关(可能导致与`HashSet`行为不一致)。 - **示例**: ```java Set<Integer> set = new TreeSet<>(); set.add(3); // 作为根节点 set.add(1); // 比3小 → 成为左子节点 set.add(1); // 比较结果为0 → 去 ``` ### 关键区别总结 | **特性** | **HashSet** | **TreeSet** | |------------------|------------------------------------------|------------------------------------------| | **底层结构** | 哈希表(HashMap) | 红黑树 | | **顺序保证** | 无(遍历顺序不可预测) | 自然排序或自定义排序 | | **去依据** | `hashCode()` + `equals()` | 比较器返回值(`compareTo`或`compare`) | | **时间复杂度** | 添加/删除/查找:平均$O(1)$,最坏$O(n)$ | 添加/删除/查找:$O(\log n)$ | | **元素要求** | 需正确写`hashCode()`和`equals()` | 元素可比较(实现`Comparable`或提供比较器)| §§相关问题§§ 1. 哈希冲突的常见解决方法有哪些? 2. 红黑树如何保证自平衡? 3. 为什么`TreeSet`判断重复元素时不使用`equals()`方法? </think>### HashSet和TreeSet存储方式详解 #### 1. **HashSet的存储机制** - **底层结构**:基于哈希表实现(本质是`HashMap`实例)[^2] - **存储原理**: 1. 添加元素时调用对象的`hashCode()`方法计算哈希值 2. 根据哈希值确定元素在哈希桶中的位置 3. 处理哈希冲突: - 同一桶位存储多个元素时形成链表(Java 8前) - 链表长度超过阈值(默认8)转为红黑树(Java 8+优化)[^3] - **去规则**: - 同时满足两个条件才重复: $$ \text{重复定} = (\text{hashCode相等}) \land (\text{equals返回true}) $$ - 示例:添加两个`new String("A")`,因`hashCode`和`equals`相同,视为重复[^3] - **特点**: - 无序存储(遍历顺序与添加顺序无关) - 平均时间复杂度:$O(1)$(插入/查找) - 内存结构示意图: ``` 桶0: [元素A] → [元素B] (链表) 桶1: [红黑树根节点] (树化后) 桶2: 空 ... ``` #### 2. **TreeSet存储机制** - **底层结构**:基于红黑树(自平衡二叉搜索树)[^1][^4] - **存储原理**: 1. 添加元素时通过比较器(`Comparator`)或自然排序(`Comparable`)确定节点位置 2. 递归比较树节点: - 小于当前节点 → 向左子树移动 - 大于当前节点 → 向右子树移动 - 等于当前节点(`compareTo`返回0)→ 重复 3. 插入后通过旋转/变色保持树平衡 - **去规则**: - 仅依赖比较结果:`compareTo()`或`compare()`返回0即重复[^4] - 与`equals()`方法解耦(可能导致与`HashSet`行为不一致) - **特点**: - 自动排序(遍历时按升序输出) - 时间复杂度:$O(\log n)$(所有操作) - 内存结构示意图: ``` [元素B] / \ [元素A] [元素C] ``` #### 3. **关键对比总结** | 特性 | HashSet | TreeSet | |--------------------|-----------------------------|-----------------------------| | **底层数据结构** | 哈希表 + 链表/红黑树[^2][^3] | 红黑树[^1][^4] | | **排序能力** | 无序 | 自然排序/自定义排序 | | **去依据** | hashCode()+equals() | compareTo()/compare() | | **插入复杂度** | $O(1)$ (平均) | $O(\log n)$ | | **内存开销** | 较低(桶数组+节点) | 较高(树节点存储父子指针) | | **允许null元素** | 是 | 否(需比较器支持) | > 示例验证代码: > ```java > // HashSet验证无序性 > Set<Integer> hashSet = new HashSet<>(); > hashSet.add(3); hashSet.add(1); hashSet.add(2); > System.out.println(hashSet); // 可能输出 [1, 2, 3] 或 [3, 1, 2]... > > // TreeSet验证有序性 > Set<Integer> treeSet = new TreeSet<>(); > treeSet.add(3); treeSet.add(1); treeSet.add(2); > System.out.println(treeSet); // 固定输出 [1, 2, 3] > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值