一、核心机制补充
- HashSet的扩容与树化
· 初始容量:16(可指定)
· 负载因子:0.75(当元素个数 ≥ 容量×0.75 时扩容为2倍)
· 链表→红黑树:JDK 8+,当某个桶的链表长度 ≥ 8 且总元素 ≥ 64 时,转为红黑树(复杂度O(log n));树节点少于6时退化为链表。
- TreeSet的排序依赖
· 元素必须实现 Comparable,或者构造时传入 Comparator。
· 判断重复依据 compareTo() / compare() 返回0,而非 equals()。这可能导致与 equals() 语义不一致,放入Set后 contains() 会按照比较器逻辑判定。
- 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,线程安全且高效
四、常见陷阱与最佳实践
- 可变对象作为元素
问题:将对象加入HashSet后,修改其属性导致哈希值变化,将无法被删除或正确查找(对象"丢失")。
解决:若需要修改,先移除,修改后再重新添加。
- 自定义equals/hashCode规范
· 使用 Objects.hash(属性1, 属性2) 生成hashCode。
· equals必须自反、对称、传递、非空。
· HashSet判定流程:hashCode不同 → 不同对象;hashCode相同 → 调用equals确认。
- TreeSet中null的处理
· 自然排序时,TreeSet 不允许null(因为无法比较)。
· 如果自定义Comparator能处理null(如 Comparator.nullsLast),则可以存放一个null。
- 初始容量预估
```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集合常见的代码
- 基本使用与去重
```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);
```
- 三种遍历方式
```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);
```
- 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] (注意长度相同只保留一个)
```
- LinkedHashSet:保持插入顺序
```java
Set<String> set = new LinkedHashSet<>();
set.add("B");
set.add("A");
set.add("C");
System.out.println(set); // [B, A, C] -- 按插入顺序
```
- 集合运算(并、交、差)
```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]
```
- 自定义对象去重(重写 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
```
- 线程安全包装
```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); // 迭代时无需额外同步
```
- 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]
```
- 不可变 Set(JDK 9+)
```java
Set<String> set = Set.of("A", "B", "C"); // 不可变,无null
// set.add("D"); // 抛出 UnsupportedOperationException
Set<String> copy = Set.copyOf(anotherSet); // 复制后不可变
```
- 判断重复的原理(面试手写)
```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;
}
```