Java Set集合相关知识点

一、核心机制补充

  1. HashSet的扩容与树化

· 初始容量:16(可指定)

· 负载因子:0.75(当元素个数 ≥ 容量×0.75 时扩容为2倍)

· 链表→红黑树:JDK 8+,当某个桶的链表长度 ≥ 8 且总元素 ≥ 64 时,转为红黑树(复杂度O(log n));树节点少于6时退化为链表。

  1. TreeSet的排序依赖

· 元素必须实现 Comparable,或者构造时传入 Comparator。

· 判断重复依据 compareTo() / compare() 返回0,而非 equals()。这可能导致与 equals() 语义不一致,放入Set后 contains() 会按照比较器逻辑判定。

  1. LinkedHashSet的额外开销

· 维护双向链表,占用更多内存,但迭代顺序与插入顺序一致(非同步)。

二、集合操作(数学集合运算)

Set直接支持:

```java

// 并集

setA.addAll(setB);

// 交集

setA.retainAll(setB);

// 差集 (A - B)

setA.removeAll(setB);

// 对称差 (A∪B - A∩B)

Set<String> symmetricDiff = new HashSet<>(setA);

symmetricDiff.addAll(setB);

Set<String> tmp = new HashSet<>(setA);

tmp.retainAll(setB);

symmetricDiff.removeAll(tmp);

```

三、线程安全方案对比

方法 特点

Collections.synchronizedSet 简单包装,所有方法加锁,读操作也同步

CopyOnWriteArraySet 读多写少场景,写时复制底层数组,迭代器无需加锁,但内存占用高

ConcurrentHashMap.newKeySet() JDK 8+,基于ConcurrentHashMap,线程安全且高效

四、常见陷阱与最佳实践

  1. 可变对象作为元素

问题:将对象加入HashSet后,修改其属性导致哈希值变化,将无法被删除或正确查找(对象"丢失")。

解决:若需要修改,先移除,修改后再重新添加。

  1. 自定义equals/hashCode规范

· 使用 Objects.hash(属性1, 属性2) 生成hashCode。

· equals必须自反、对称、传递、非空。

· HashSet判定流程:hashCode不同 → 不同对象;hashCode相同 → 调用equals确认。

  1. TreeSet中null的处理

· 自然排序时,TreeSet 不允许null(因为无法比较)。

· 如果自定义Comparator能处理null(如 Comparator.nullsLast),则可以存放一个null。

  1. 初始容量预估

```java

// 已知元素数量为N,避免频繁扩容

Set<String> set = new HashSet<>(N * 4 / 3 + 1);

```

五、部分高级方法(NavigableSet接口)

TreeSet实现了NavigableSet,提供:

· lower(e):小于e的最大元素

· floor(e):小于等于e的最大元素

· higher(e):大于e的最小元素

· ceiling(e):大于等于e的最小元素

· subSet(from, true, to, false):范围子集

六、不可变Set(JDK 9+)

```java

Set<String> set = Set.of("a", "b", "c"); // 不可变,无null

Set<String> copy = Set.copyOf(otherSet); // 复制后不可变

```

注意:不可变Set中包含重复或null会抛异常。

七、内存占用对比(粗略)

类型 每元素额外开销

HashSet 较高(哈希桶+Node对象)

LinkedHashSet 更高(额外前后指针)

TreeSet 最高(红黑树节点+颜色位)

EnumSet 极低(位向量),专门优化枚举类型

Set集合常见的代码

  1. 基本使用与去重

```java

// HashSet:去重,不保证顺序

Set<String> set = new HashSet<>();

set.add("A");

set.add("B");

set.add("A"); // 重复,不会添加

System.out.println(set.size()); // 2

// 利用Set去重List

List<Integer> list = Arrays.asList(1, 2, 2, 3);

Set<Integer> unique = new HashSet<>(list);

```

  1. 三种遍历方式

```java

Set<String> set = new HashSet<>();

set.add("A"); set.add("B");

// 增强for

for (String s : set) {

System.out.println(s);

}

// 迭代器

Iterator<String> it = set.iterator();

while (it.hasNext()) {

System.out.println(it.next());

}

// Lambda (Java 8+)

set.forEach(System.out::println);

```

  1. TreeSet:自然排序 + 自定义排序

```java

// 自然排序(元素实现Comparable)

Set<Integer> numbers = new TreeSet<>();

numbers.add(5); numbers.add(1); numbers.add(3);

System.out.println(numbers); // [1, 3, 5]

// 自定义排序(按字符串长度)

Set<String> set = new TreeSet<>(Comparator.comparingInt(String::length));

set.add("abc");

set.add("a");

set.add("abcd");

System.out.println(set); // [a, abc, abcd] (注意长度相同只保留一个)

```

  1. LinkedHashSet:保持插入顺序

```java

Set<String> set = new LinkedHashSet<>();

set.add("B");

set.add("A");

set.add("C");

System.out.println(set); // [B, A, C] -- 按插入顺序

```

  1. 集合运算(并、交、差)

```java

Set<Integer> a = new HashSet<>(Arrays.asList(1,2,3));

Set<Integer> b = new HashSet<>(Arrays.asList(3,4,5));

// 并集

Set<Integer> union = new HashSet<>(a);

union.addAll(b); // [1,2,3,4,5]

// 交集

Set<Integer> inter = new HashSet<>(a);

inter.retainAll(b); // [3]

// 差集 (a - b)

Set<Integer> diff = new HashSet<>(a);

diff.removeAll(b); // [1,2]

```

  1. 自定义对象去重(重写 equals 和 hashCode)

```java

class Person {

String name;

int age;

Person(String name, int age) { this.name = name; this.age = age; }

@Override

public boolean equals(Object o) {

if (this == o) return true;

if (!(o instanceof Person)) return false;

Person p = (Person) o;

return age == p.age && Objects.equals(name, p.name);

}

@Override

public int hashCode() {

return Objects.hash(name, age);

}

}

// 使用

Set<Person> set = new HashSet<>();

set.add(new Person("Alice", 20));

set.add(new Person("Alice", 20)); // 重复,不会被加入

System.out.println(set.size()); // 1

```

  1. 线程安全包装

```java

Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());

// 迭代时仍需手动同步

synchronized (syncSet) {

for (String s : syncSet) { /* ... */ }

}

// 并发包中的 CopyOnWriteArraySet(读多写少)

Set<String> cowSet = new CopyOnWriteArraySet<>();

cowSet.add("X");

cowSet.forEach(System.out::println); // 迭代时无需额外同步

```

  1. TreeSet 的 NavigableSet 方法(范围查找)

```java

TreeSet<Integer> set = new TreeSet<>(Arrays.asList(1,3,5,7,9));

System.out.println(set.ceiling(4)); // 5 (≥4的最小值)

System.out.println(set.floor(4)); // 3 (≤4的最大值)

System.out.println(set.higher(5)); // 7

System.out.println(set.lower(5)); // 3

System.out.println(set.subSet(3, true, 7, false)); // [3,5]

```

  1. 不可变 Set(JDK 9+)

```java

Set<String> set = Set.of("A", "B", "C"); // 不可变,无null

// set.add("D"); // 抛出 UnsupportedOperationException

Set<String> copy = Set.copyOf(anotherSet); // 复制后不可变

```

  1. 判断重复的原理(面试手写)

```java

// HashSet 的 add 方法逻辑(简化版)

public boolean add(E e) {

int h = e.hashCode();

int index = (h & (table.length - 1));

if (table[index] == null) {

table[index] = e;

return true;

}

// 如果该位置已有元素,则遍历链表/树,调用 equals 比较

for (Node node = table[index]; node != null; node = node.next) {

if (node.hash == h && (node.key == e || node.key.equals(e))) {

return false; // 重复,不添加

}

}

// 未找到重复,添加

table[index] = new Node(e);

return true;

}

```

相关推荐
Linsk1 小时前
Java和JavaScript的关系真是雷峰和雷峰塔的关系吗?
java·javascript·oracle
许彰午2 小时前
我手写了一个 Java 内存数据库(二):B+ 树的插入与分裂
java·开发语言·面试
zhouwy1132 小时前
Java 快速入门笔记:从基础语法到 Spring Boot 实战
java
大飞记Python2 小时前
【2026更新】Python基础学习指南(AI版)——04数据类型
开发语言·人工智能·python
极创信息2 小时前
信创产品认证怎么做?信创产品测试认证的主要流程
java·大数据·数据库·金融·软件工程
生成论实验室2 小时前
《事件关系阴阳博弈动力学:识势应势之道》第四篇:降U动力学——认知确定度的自驱演化
人工智能·科技·神经网络·算法·架构
AI科技星2 小时前
全域数学·72分册:场计算机卷【乖乖数学】
算法·机器学习·数学建模·数据挖掘·量子计算
SamDeepThinking2 小时前
并发量就算只有2,该上锁还得上呀
java·后端·架构
Alice-YUE2 小时前
【js高频八股】防抖与节流
开发语言·前端·javascript·笔记·学习·ecmascript