copyonwritearrayset内部持有一个copyonwritearraylist实例,通过手动检查元素是否存在(基于equals())实现去重,add/remove/contains均为o(n)时间复杂度,但遍历线程安全、读操作零同步。

CopyOnWriteArraySet 本身并不直接基于 CopyOnWriteArrayList 实现去重,而是**内部持有一个 CopyOnWriteArrayList 实例,并借助其线程安全的写时复制机制 + 手动去重逻辑来保证集合语义**。
底层用的是 CopyOnWriteArrayList,但去重靠自己判断
CopyOnWriteArraySet 的源码中有一个私有 final 成员:
private final CopyOnWriteArrayList它不是“复用 ArrayList 的去重能力”(List 本就不去重),而是所有添加、删除操作都委托给这个 al,但在 add() 等方法里**主动检查元素是否已存在**,仅当不存在时才真正调用 al.add()。
例如 add() 方法逻辑类似:
- 先调用 al.indexOf(element) 判断是否已存在(返回 -1 表示没有)
- 如果不存在,再调用 al.add(element) —— 这一步会触发 CopyOnWriteArrayList 的写时复制:新建数组、拷贝旧数据、追加新元素
- 返回 true 表示新增成功;否则返回 false
为什么不用 HashSet 或其他结构?
因为 CopyOnWriteArraySet 的核心诉求是:在**遍历不加锁、写操作线程安全、适合读多写少场景**的前提下提供 Set 语义。HashSet 虽然去重快,但迭代器不是弱一致性,且写操作需同步;而 CopyOnWriteArrayList 天然支持安全迭代(始终基于快照),所以选它作底座。
代价是:add/remove 时间复杂度为 O(n),因为每次都要 scan 整个数组查重;但换来的是读操作零同步、迭代绝对安全。
去重依赖 equals(),不是 ==
和所有 Set 一样,CopyOnWriteArraySet 判重完全基于元素的 equals() 方法(同时要求 hashCode() 与 equals 一致)。它调用的是 al.indexOf(),而 indexOf 内部用的就是 equals() 逐个比较。
所以自定义类做元素时,必须正确重写 equals 和 hashCode,否则可能重复添加或查找不到。
写时复制 + 查重 = 安全但较慢的 Set
- 每次 add 都要遍历当前数组(O(n)),再执行一次写时复制(O(n) 拷贝)→ 总体 O(n)
- remove 同样先 indexOf 再 remove(index),也是 O(n)
- contains 直接调用 al.indexOf,O(n),但无需加锁,读并发极高
- 迭代器直接返回 al 的 iterator,返回的是快照,即使其他线程正在 add/remove,也不影响本次遍历
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











