问题

(1)CopyOnWriteArraySet是用Map实现的吗?

(2)CopyOnWriteArraySet是有序的吗?

(3)CopyOnWriteArraySet是并发安全的吗?

(4)CopyOnWriteArraySet以何种方式保证元素不重复?

(5)如何比较两个Set中的元素是否完全一致?

简介

CopyOnWriteArraySet底层是使用CopyOnWriteArrayList存储元素的,所以它并不是使用Map来存储元素的。

但是,我们知道CopyOnWriteArrayList底层其实是一个数组,它是允许元素重复的,那么用它来实现CopyOnWriteArraySet怎么保证元素不重复呢?

CopyOnWriteArrayList回顾请点击【死磕 java集合之CopyOnWriteArrayList源码分析】。

源码分析

Set类的源码一般都比较短,所以我们直接贴源码上来一行一行分析吧。

Set之类的简单源码适合泛读,主要是掌握一些不常见的用法,做到心里有说,坐个车三五分钟可能就看完了。

像ConcurrentHashMap、ConcurrentSkipListMap之类的比较长的我们还是倾向分析主要的方法,适合精读,主要是掌握实现原理以及一些不错的思想,可能需要一两个小时才能看完一整篇文章。

public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
private static final long serialVersionUID = 5457747651344034263L; // 内部使用CopyOnWriteArrayList存储元素
private final CopyOnWriteArrayList<E> al; // 构造方法
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
} // 将集合c中的元素初始化到CopyOnWriteArraySet中
public CopyOnWriteArraySet(Collection<? extends E> c) {
if (c.getClass() == CopyOnWriteArraySet.class) {
// 如果c是CopyOnWriteArraySet类型,说明没有重复元素,
// 直接调用CopyOnWriteArrayList的构造方法初始化
@SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc =
(CopyOnWriteArraySet<E>)c;
al = new CopyOnWriteArrayList<E>(cc.al);
}
else {
// 如果c不是CopyOnWriteArraySet类型,说明有重复元素
// 调用CopyOnWriteArrayList的addAllAbsent()方法初始化
// 它会把重复元素排除掉
al = new CopyOnWriteArrayList<E>();
al.addAllAbsent(c);
}
} // 获取元素个数
public int size() {
return al.size();
} // 检查集合是否为空
public boolean isEmpty() {
return al.isEmpty();
} // 检查是否包含某个元素
public boolean contains(Object o) {
return al.contains(o);
} // 集合转数组
public Object[] toArray() {
return al.toArray();
} // 集合转数组,这里是可能有bug的,详情见ArrayList中分析
public <T> T[] toArray(T[] a) {
return al.toArray(a);
} // 清空所有元素
public void clear() {
al.clear();
} // 删除元素
public boolean remove(Object o) {
return al.remove(o);
} // 添加元素
// 这里是调用CopyOnWriteArrayList的addIfAbsent()方法
// 它会检测元素不存在的时候才添加
// 还记得这个方法吗?当时有分析过的,建议把CopyOnWriteArrayList拿出来再看看
public boolean add(E e) {
return al.addIfAbsent(e);
} // 是否包含c中的所有元素
public boolean containsAll(Collection<?> c) {
return al.containsAll(c);
} // 并集
public boolean addAll(Collection<? extends E> c) {
return al.addAllAbsent(c) > 0;
} // 单方向差集
public boolean removeAll(Collection<?> c) {
return al.removeAll(c);
} // 交集
public boolean retainAll(Collection<?> c) {
return al.retainAll(c);
} // 迭代器
public Iterator<E> iterator() {
return al.iterator();
} // equals()方法
public boolean equals(Object o) {
// 如果两者是同一个对象,返回true
if (o == this)
return true;
// 如果o不是Set对象,返回false
if (!(o instanceof Set))
return false;
Set<?> set = (Set<?>)(o);
Iterator<?> it = set.iterator(); // 集合元素数组的快照
Object[] elements = al.getArray();
int len = elements.length; // 我觉得这里的设计不太好
// 首先,Set中的元素本来就是不重复的,所以不需要再用个matched[]数组记录有没有出现过
// 其次,两个集合的元素个数如果不相等,那肯定不相等了,这个是不是应该作为第一要素先检查
boolean[] matched = new boolean[len];
int k = 0;
// 从o这个集合开始遍历
outer: while (it.hasNext()) {
// 如果k>len了,说明o中元素多了
if (++k > len)
return false;
// 取值
Object x = it.next();
// 遍历检查是否在当前集合中
for (int i = 0; i < len; ++i) {
if (!matched[i] && eq(x, elements[i])) {
matched[i] = true;
continue outer;
}
}
// 如果不在当前集合中,返回false
return false;
}
return k == len;
} // 移除满足过滤条件的元素
public boolean removeIf(Predicate<? super E> filter) {
return al.removeIf(filter);
} // 遍历元素
public void forEach(Consumer<? super E> action) {
al.forEach(action);
} // 分割的迭代器
public Spliterator<E> spliterator() {
return Spliterators.spliterator
(al.getArray(), Spliterator.IMMUTABLE | Spliterator.DISTINCT);
} // 比较两个元素是否相等
private static boolean eq(Object o1, Object o2) {
return (o1 == null) ? o2 == null : o1.equals(o2);
}
}

可以看到,在添加元素时调用了CopyOnWriteArrayList的addIfAbsent()方法来保证元素不重复。

还记得这个方法的实现原理吗?点击直达【死磕 java集合之CopyOnWriteArrayList源码分析】。

总结

(1)CopyOnWriteArraySet是用CopyOnWriteArrayList实现的;

(2)CopyOnWriteArraySet是有序的,因为底层其实是数组,数组是不是有序的?!

(3)CopyOnWriteArraySet是并发安全的,而且实现了读写分离;

(4)CopyOnWriteArraySet通过调用CopyOnWriteArrayList的addIfAbsent()方法来保证元素不重复;

彩蛋

(1)如何比较两个Set中的元素是否完全相等?

假设有两个Set,一个是A,一个是B。

最简单的方式就是判断是否A中的元素都在B中,B中的元素是否都在A中,也就是两次两层循环。

其实,并不需要。

因为Set中的元素并不重复,所以只要先比较两个Set的元素个数是否相等,再作一次两层循环就可以了,需要仔细体味。代码如下:

public class CopyOnWriteArraySetTest {

    public static void main(String[] args) {
Set<Integer> set1 = new CopyOnWriteArraySet<>();
set1.add(1);
set1.add(5);
set1.add(2);
set1.add(7);
// set1.add(3);
set1.add(4); Set<Integer> set2 = new HashSet<>();
set2.add(1);
set2.add(5);
set2.add(2);
set2.add(7);
set2.add(3); System.out.println(eq(set1, set2)); System.out.println(eq(set2, set1));
} private static <T> boolean eq(Set<T> set1, Set<T> set2) {
if (set1.size() != set2.size()) {
return false;
} for (T t : set1) {
// contains相当于一层for循环
if (!set2.contains(t)) {
return false;
}
} return true;
}
}

(2)那么,如何比较两个List中的元素是否完全相等呢?

我们知道,List中元素是可以重复的,那是不是要做两次两层循环呢?

其实,也不需要做两次两层遍历,一次也可以搞定,设定一个标记数组,标记某个位置的元素是否找到过,请仔细体味。代码如下:

public class ListEqTest {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(3);
list1.add(6);
list1.add(3);
list1.add(8);
list1.add(5); List<Integer> list2 = new ArrayList<>();
list2.add(3);
list2.add(1);
list2.add(3);
list2.add(8);
list2.add(5);
list2.add(6); System.out.println(eq(list1, list2));
System.out.println(eq(list2, list1));
} private static <T> boolean eq(List<T> list1, List<T> list2) {
if (list1.size() != list2.size()) {
return false;
} // 标记某个元素是否找到过,防止重复
boolean matched[] = new boolean[list2.size()]; outer: for (T t : list1) {
for (int i = 0; i < list2.size(); i++) {
// i这个位置没找到过才比较大小
if (!matched[i] && list2.get(i).equals(t)) {
matched[i] = true;
continue outer;
}
}
return false;
} return true;
}
}

这种设计是不是很巧妙?^^


欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计的更多相关文章

  1. 死磕 java集合之DelayQueue源码分析

    问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...

  2. 死磕 java集合之PriorityBlockingQueue源码分析

    问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...

  3. 死磕 java集合之PriorityQueue源码分析

    问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...

  4. 死磕 java集合之LinkedHashSet源码分析

    问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...

  5. 死磕 java集合之ConcurrentHashMap源码分析(三)

    本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...

  6. 死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  7. 【死磕 Java 集合】— ConcurrentSkipListMap源码分析

    转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...

  8. 死磕 java集合之LinkedList源码分析

    问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedL ...

  9. 死磕 java集合之ConcurrentSkipListSet源码分析——Set大汇总

    问题 (1)ConcurrentSkipListSet的底层是ConcurrentSkipListMap吗? (2)ConcurrentSkipListSet是线程安全的吗? (3)Concurren ...

随机推荐

  1. Java Script 读书笔记 (四) 面向对象编程

    1. 对象,属性 前面看到对象里删除属性一直疑惑,什么是对象,为什么属性可以删除, 我印象里的属性还是停留在property, 总想不明白为什么属性竟然能够删除.直到看到标准库才明白,原来对象就是py ...

  2. linux.go

    func LockFile(file *os.File) error { return syscall.Flock(int(file.Fd()), syscall.LOCK_EX) }

  3. bzoj 1901 主席树+树状数组

    修改+查询第k小值 单纯主席树修改会打乱所有,所以再套一个树状数组维护前缀和使得修改,查询都是log 对了,bzoj上不需要读入组数,蜜汁re.. #include<cstdio> #in ...

  4. BZOJ_2721_[Violet 5]樱花_数学

    BZOJ_2721_[Violet 5]樱花_数学 Description Input Output $\frac{1}{x}+\frac{1}{y}=\frac{1}{m}$ $xm+ym=xy$ ...

  5. BZOJ_3589_动态树_容斥原理+树链剖分

    BZOJ_3589_动态树_容斥原理+树链剖分 题意: 维护一棵树,支持1.子树内点权加上一个数  2.给出k条链,求路径上的点权和(重复的计算一次) (k<=5) 分析: 可以用树剖+线段树解 ...

  6. Java基础-工厂设计模式(三锅的肥鸡)

    ---恢复内容开始---   1)还没有工厂时代:假如还没有工业革命,如果一个你要一架飞机,一般的做法是自己去建造一架飞机,然后拿来开 通常的结果就是 有些时候 要么专科螺钉 没打好  要么就是 那个 ...

  7. Python从入门到放弃

    计算机基础 01 计算机基础之编程 02 计算机组成原理 03 计算机操作系统 04 编程语言分类 Python解释器 05 Python和Python解释器 06 执行Python程序的两种方式 0 ...

  8. TypeScript 实现任务队列

    业务中经常会有一些批量操作的任务,比如使用 JavaScript 预加载一组图片,批量上传一些资源.如果这些任务一次性启动,势必会消耗很多资源和带宽.理想的做法应该对这些任务进行限制,比如一次只跑几个 ...

  9. Bootstrap优秀模板-Unify.2.6.2

    这是一个非常老牌的Bootstrap商业模板,全面性和稳定性俱佳,有LandingPage.BussinessPage.AdminPage多种模式,非常推荐用来构建官网.响应式应用Web.管理端Web ...

  10. Java - 静态代理详讲

    Java - 静态代理详讲 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 写在前面:*此章内容比较抽象,所以需要结合实际操作进行讲解*                   *需要有 ...