一句话说透Java里面的Set集合是如何保证元素不重复的

160 阅读2分钟

一句话总结:

「Set通过equalshashCode(或比较器)来判重,底层数据结构决定具体如何实现。」**


一、HashSet:哈希表判重

1. 核心机制

  • 底层结构:基于HashMap(数组+链表/红黑树)。

  • 判重规则

    1. 哈希冲突检测:通过hashCode()计算键的哈希值,定位到数组位置。
    2. 内容判等:若该位置已有元素,再用equals()逐个比较内容。

2. 示例代码

Set<String> set = new HashSet<>();
set.add("苹果");
set.add("苹果"); // 重复元素,添加失败
// 最终集合内容:["苹果"]

3. 关键条件

  • 自定义对象必须重写hashCode()equals()

    class User {
        String name;
        int age;
    
        @Override
        public int hashCode() {
            return Objects.hash(name, age); // 根据name和age生成哈希值
        }
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            User user = (User) obj;
            return age == user.age && Objects.equals(name, user.name);
        }
    }
    
  • 若未重写:默认使用对象内存地址判断,导致内容相同的对象被误判为不同。


二、TreeSet:红黑树判重

1. 核心机制

  • 底层结构:基于TreeMap(红黑树)。

  • 判重规则

    • 自然排序:元素实现Comparable接口,通过compareTo()返回值是否为0判重。
    • 自定义比较器:通过Comparator.compare()返回值是否为0判重。

2. 示例代码

// 自然排序(Integer已实现Comparable)
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(3);
treeSet.add(3); // 重复元素,添加失败

// 自定义比较器(按字符串长度排序)
Set<String> customSet = new TreeSet<>((a, b) -> a.length() - b.length());
customSet.add("Apple"); // 长度5
customSet.add("Banana"); // 长度6
customSet.add("Cat"); // 长度3 → 允许添加(长度不同)
// 集合内容:["Cat", "Apple", "Banana"]

3. 关键条件

  • 元素必须可比较

    • 实现Comparable接口,或构造时传入Comparator
  • compareTo()compare()返回0时视为重复

    // 错误示例:按字符串长度排序时,相同长度的不同字符串会被误判为重复
    Set<String> badSet = new TreeSet<>((a, b) -> a.length() - b.length());
    badSet.add("Apple"); // 长度5
    badSet.add("Hello"); // 长度5 → 添加失败(被误判为重复)
    

三、LinkedHashSet:继承HashSet的判重逻辑

  • 底层结构:基于LinkedHashMap(哈希表+双向链表)。
  • 判重规则:与HashSet完全相同,额外维护插入顺序。

四、总结

Set类型判重方式适用场景
HashSethashCode() + equals()快速判重,不关心顺序
TreeSetComparable/Comparator需要排序或自定义判重规则
LinkedHashSethashCode() + equals()需要保留插入顺序

五、注意事项

  1. 哈希冲突≠重复元素:哈希值相同但内容不同的对象会被放到同一桶(链表或树中)。
  2. 不可变对象更安全:若对象存入Set后发生修改,可能导致哈希值变化,引发内存泄漏或重复元素。
  3. 线程不安全:多线程操作需用Collections.synchronizedSet()ConcurrentHashMap.newKeySet()包装。

口诀
「Set去重有妙招,Hash、Tree各不同
哈希判等两步走,红黑比较定乾坤
自定义类要小心,重写等值哈希值!」