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 (tableindex == null) {

tableindex = e;

return true;

}

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

for (Node node = tableindex; node != null; node = node.next) {

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

return false; // 重复,不添加

}

}

// 未找到重复,添加

tableindex = new Node(e);

return true;

}

```

相关推荐
Sagittarius_A*9 分钟前
后量子密码|通识认知 03|量子威胁辟谣:Grover 算法对对称密码的影响与误区
算法·信息安全·密码学·量子计算·pqc·后量子密码
Patrick在香港15 分钟前
Python Docker镜像从1.2GB到89MB:多阶段构建的完整优化实录
java·python·docker·信息可视化·容器·数据分析·ai编程
上海安当技术26 分钟前
敏感数据怎么防拖库?信封加密(DEK+KEK 二层密钥)架构设计与 Java 实战
java·开发语言·python
我是唐青枫35 分钟前
Java JCommander 实战详解:用注解解析命令行参数
java·开发语言
大鹏说大话43 分钟前
C++ 内存布局详解:类、虚函数、虚表底层原理
java·开发语言
键盘会跳舞1 小时前
C++:容器适配器 queue 的源码级拆解
开发语言·c++·queue
还是鼠鼠1 小时前
Spring AI连接DeepSeek与通义千问:application.yaml配置详解
java·通义千问·spring ai·deepseek
QAQo7T2 小时前
Python组蓝桥杯备赛超详细知识点总结笔记_排序算法篇
笔记·python·算法·蓝桥杯·排序算法
千里之行,始于足下sanhai2 小时前
P8686 [蓝桥杯 2019 省 A] 修改数组 - 数字魔法大冒险 题解
c++·算法·动态规划
小星星闪亮登场2 小时前
Codeforces Round 1115 ( Div. 2)
数据结构·c++·算法·贪心算法·排序算法·codeforces