云计算百科
云计算领域专业知识百科平台

【集合框架Set接口】

👉 想存一堆用户,但发现有重复的?
👉 用 Set 去重,但结果顺序乱了?
👉 面试官问:“HashSet 和 TreeSet 有什么区别?” 你只会说“一个无序一个有序”?


一、Set 是什么?—— 无序不可重复的“集合”

🎯 核心特点

  • 无序(Unordered):不保证元素的插入顺序(某些实现如 LinkedHashSet 除外)
  • 不允许重复:每个元素在集合中最多出现一次
  • 基于 equals() 和 hashCode() 判断重复

✅ 典型场景:

  • 用户去重
  • 标签集合
  • 权限集合

🖼️ 图示:Set 的逻辑结构

+——–+ +——–+ +——–+
| Apple | | Banana | | Orange |
+——–+ +——–+ +——–+

✅ 核心操作:

  • 添加元素:add(E e) —— 如果元素已存在,返回 false
  • 删除元素:remove(Object o)
  • 判断是否包含:contains(Object o)

二、Set 的核心实现类

1. HashSet —— 基于“哈希表”的实现(最常用)

🎯 核心特性
  • 底层是 HashMap:元素作为 HashMap 的 key 存储
  • 添加、删除、查找极快:平均时间复杂度 O(1)
  • 不保证顺序
  • 允许一个 null 值

Set<String> set = new HashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Apple"); // 重复,添加失败,返回 false
System.out.println(set.size()); // 输出 2

🔍 去重原理详解

// HashSet 内部持有一个 HashMap
private transient HashMap<E,Object> map;

// add 方法本质是 put 到 HashMap 的 key
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}

✅ PRESENT 是一个静态常量,所有 key 共享同一个 value。

🖼️ 图示:HashSet 的底层结构

HashSet
|
v
HashMap
+—————–+
| key: "Apple" | -> value: PRESENT (常量)
+—————–+
| key: "Banana" | -> value: PRESENT
+—————–+
| key: "Orange" | -> value: PRESENT
+—————–+

✅ 去重依赖 hashCode() 和 equals():

  • 先计算 hashCode(),定位桶
  • 在桶内遍历链表/树,用 equals() 判断是否相等
  • ❌ 经典误区:自定义对象未重写 hashCode() 和 equals()

    class User {
    String name;
    int age;
    // 未重写 hashCode 和 equals
    }

    Set<User> users = new HashSet<>();
    users.add(new User("Alice", 25));
    users.add(new User("Alice", 25)); // 被认为是两个不同的对象!

    ✅ 正确做法:重写 hashCode() 和 equals(),确保逻辑相等的对象哈希值也相等。


    2. LinkedHashSet —— HashSet + 双向链表(维护插入顺序)

    🎯 核心特性
    • 继承 HashSet,在 HashMap 基础上增加双向链表
    • 维护插入顺序
    • 性能接近 HashSet,但内存稍大(维护链表)

    Set<String> set = new LinkedHashSet<>();
    set.add("C");
    set.add("A");
    set.add("B");
    System.out.println(set); // 输出 [C, A, B](有序!)

    🖼️ 图示:LinkedHashSet 的底层结构

    LinkedHashSet
    |
    v
    LinkedHashMap (继承 HashMap)
    +—————–+
    | key: "C" | -> value: PRESENT
    | (linked to A) |
    +—————–+
    | key: "A" | -> value: PRESENT
    | (linked to B) |
    +—————–+
    | key: "B" | -> value: PRESENT
    | (linked to null)|
    +—————–+

    双向链表:C <-> A <-> B

    ✅ 适用场景:需要去重且保持插入顺序,如最近访问记录、配置项。


    3. TreeSet —— 基于“红黑树”的实现(自动排序)

    🎯 核心特性
    • 底层是 TreeMap:基于红黑树(Red-Black Tree)
    • 元素自动排序:自然序(Comparable)或自定义比较器(Comparator)
    • 不允许 null 值(因为比较时会抛出 NullPointerException)
    • 性能 O(log n),比 HashSet 慢

    Set<Integer> set = new TreeSet<>();
    set.add(3);
    set.add(1);
    set.add(2);
    System.out.println(set); // 输出 [1, 2, 3](有序!)

    🖼️ 图示:TreeSet 的底层结构(红黑树)

    (2)
    / \\
    (1) (3)
    / \\ / \\
    null null null null

    ✅ 排序规则:

    • 自然序:实现 Comparable 接口
    • 自定义序:传入 Comparator

    // 自定义比较器:按字符串长度排序
    Set<String> set = new TreeSet<>((a, b) -> a.length() – b.length());
    set.add("hi");
    set.add("hello");
    set.add("a");
    System.out.println(set); // 输出 [a, hi, hello]

    ✅ 适用场景:需要排序的去重集合,如排行榜、优先队列、范围查询。


    三、Set 的常用方法详解

    1. 添加元素

    boolean added = set.add("Apple"); // 成功返回 true,重复返回 false

    2. 删除元素

    boolean removed = set.remove("Apple"); // 成功返回 true,不存在返回 false

    3. 判断是否包含

    boolean contains = set.contains("Apple");

    4. 集合操作(交集、并集、差集)

    Set<String> set1 = new HashSet<>(Arrays.asList("A", "B", "C"));
    Set<String> set2 = new HashSet<>(Arrays.asList("B", "C", "D"));

    // 并集:set1 ∪ set2
    Set<String> union = new HashSet<>(set1);
    union.addAll(set2); // [A, B, C, D]

    // 交集:set1 ∩ set2
    Set<String> intersection = new HashSet<>(set1);
    intersection.retainAll(set2); // [B, C]

    // 差集:set1 – set2
    Set<String> difference = new HashSet<>(set1);
    difference.removeAll(set2); // [A]


    四、高频问题 & 高分回答

    Q1: HashSet 的去重原理是什么?

    答:
    HashSet 内部持有一个 HashMap,元素作为 HashMap 的 key 存储。
    利用 HashMap 的 key 不可重复的特性来保证 HashSet 的唯一性。
    add(e) 本质是 map.put(e, PRESENT),PRESENT 是一个静态常量。


    Q2: 为什么 TreeSet 不允许 null 值?

    答:
    因为 TreeSet 需要对元素进行比较排序。
    当插入 null 时,会调用 compareTo() 方法,导致 NullPointerException。
    即使第一个元素是 null,后续插入非 null 元素时也会尝试与 null 比较,同样会抛出异常。


    Q3: LinkedHashSet 如何维护插入顺序?

    答:
    LinkedHashSet 继承自 HashSet,底层使用 LinkedHashMap。
    LinkedHashMap 在 HashMap 的基础上,增加了一条双向链表,
    将所有节点按照插入顺序串联起来,从而维护了插入顺序。


    Q4: HashSet 和 TreeSet 如何选择?

    答:

    • HashSet:追求极致性能,不关心顺序,允许 null;
    • TreeSet:需要自动排序,支持范围查询,不允许 null。
      一般情况下优先选 HashSet,只有需要排序时才用 TreeSet。

    五、总结:一张表搞懂 Set 的选型

    需求推荐实现关键点
    去重,性能优先 HashSet 基于 HashMap,O(1)
    去重,保持插入顺序 LinkedHashSet 维护双向链表
    去重,自动排序 TreeSet 基于红黑树,O(log n)

    🔚 最后一句话

    Set 接口是 Java 集合框架中“去重”功能的基石。
    从 HashSet 的哈希表,到 TreeSet 的红黑树,
    每一个实现都体现了对时间复杂度、空间复杂度、排序需求的权衡。
    只有当你真正理解了 hashCode() 和 equals() 的契约,
    以及红黑树的自平衡原理,
    你才能写出高效、正确、专业的去重逻辑!

    希望这篇能帮你彻底搞懂 Set 接口及其常见实现!

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【集合框架Set接口】
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!