CopyOnWriteArraySet是基于CopyOnWriteArrayList实现的,它能保证数组里的对象是唯一的。由于在上篇文章中对CopyOnWriteArrayList做了较详细的讲解,这里就简要分析下CopyOnWriteArraySet的原理。

一、代码结构

CopyOnWriteArraySet继承了AbstractSet,它具备Set属性和方法,最重要的是保证集合里对象唯一,这种唯一是基于equals方法比较,后面会看到源码的比较方法。

public class CopyOnWriteArraySet<E> extends AbstractSet<E>
        implements java.io.Serializable {
    private final CopyOnWriteArrayList<E> al;

    /** 构造器中初始al为CopyOnWriteArrayList */
    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
}

二、插入数据

add方法,如果集合中没有相同对象并且插入集合成功才会返回true,其他情况比如集合中已经有相同对象了,那么返回false。另外,判断集合是否有相同对象,是通过遍历集合中的每一个对象比较的,如果集合中对象比较多的话,也是比较耗性能的。

public boolean add(E e) {
		// 直接调用al的方法
        return al.addIfAbsent(e);
    }

	/** 以下代码在CopyOnWriteArrayList中 */
	
	public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }

	/** 返回给定对象在数组中的下标,如果大于等于说明数组中有相同对象 */
	private static int indexOf(Object o, Object[] elements,
                               int index, int fence) {
        if (o == null) {
        	// null对象的比较方法,直接看数组中是否有null对象
            for (int i = index; i < fence; i++)
                if (elements[i] == null)
                    return i;
        } else {
        	// 非空对象基于equals作比较
            for (int i = index; i < fence; i++)
                if (o.equals(elements[i]))
                    return i;
        }
        return -1;
    }

    /** 调用该方法说明数组中可能不存在相同的对象,我们向数组中插入新的对象 */
    private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        // 首先需要加锁
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            /** 快照和当期获取的数组不相等说明加锁之前数组被覆盖了,
            我们需要再次判断新的数组中是否有相同的对象 */
            if (snapshot != current) {
                // 取两个数组长度最小的一个
                int common = Math.min(snapshot.length, len);
                // 0到commo比较是否有相同的对象
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                // common到len比较是否有相同的对象
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
			// 到这里说明数组中没有相同的对象了,我们就复制数据到新数组,并且添加新的对象
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

三、总结

  1. CopyOnWriteArraySet我们要掌握它是基于CopyOnWriteArrayList实现的,并且在并发环境下,可以保证Set中的对象唯一。
  2. 我们需要重点关注add方法,每次调用都要遍历数组判断是否有相同的对象,比较耗性能。其他方法基本都是直接调用CopyOnWriteArrayList的对应方法。
  3. 使用场景:并发环境多读少写,要求对象唯一。