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;

}

```

相关推荐
NE_STOP14 小时前
Vide Coding--AI编程工具的选择
java
LDR00614 小时前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术14 小时前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript
通信小呆呆14 小时前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
码云数智-园园14 小时前
C++20 Modules 模块详解
java·开发语言·spring
程序员黑豆14 小时前
JDK 下载安装与配置详细教程
java·前端·ai编程
benben04415 小时前
强化学习之DQN算法族(基于gymnasium开发)
算法
小宇宙Zz15 小时前
Maven依赖冲突
java·服务器·maven
swordbob15 小时前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio