在数学上集合是一个无序、无重复和任意个具有相关特性的离散数据的组织。在数据结构中,集合是一个无序、无重复和任意个具有相关特性的对象的组织。先纵观下Set集合的框架图,如下图所示。
Set、AbstractSet
JDK中Collection都有适配器,而这里要说的是Set接口源码,细心的朋友会发现Set内的Method和Collection的Method完全相同,而它的适配器AbstractSet也只是实现了equals、hascode和removeAll三个基本方法。Set既然继承了Collection,为什么又要定义和Collection相同的接口,其实List也有这个问题?答案是先有Set、List,而后有Collection。这里也要单独说下AbsractSet,因为它的equals是根据元素是否包含在集合中,两个Set元素相同,则他们相等。
public abstract class AbstractSet<E>
extends AbstractCollection<E> implements Set<E> {
public boolean equals(Object obj) {
if(obj == this) {
return true;
} else if(!(obj instanceof Set)) {
return false;
}
Collection collection = (Collection) obj;
if(collection.size() != size()) {
return false;
}
//前面都是基本的对象比较,这里只是添加了长度比较
try {
//关键点在这里,如果obj包含于当前this对象,则也会返回true
return containAll(collection);
} catch(ClassCastException classCastException) {
return false;
} catch(NullPointerException nullPointException) {
return false;
}
}
//... ... ...
}
HashSet、TreeSet
在Set集合的实际使用中使用最广泛的就是这两个类,它们分别代表着无序和有序集合。它们内部实现都依赖于Map,并且由Map的Key来充当容器,key所对应的value为一个固定的Object对象PRESENT常量。Set保证不重复的秘诀就在于添加进来的对象的Hash值,它们在Map中进行存放时,Map会先检测key是否已经存在。TreeSet保证顺序的秘诀是TreeMap实现了红黑树结构,根据Comparator比较Key的大小来决定它们的存放顺序,并不是由存放先后顺序决定。
public class HashSet<E> extends AbstractSet<E>
implements Set<E>, Cloneable, Seriablizable {
private transient HashMap<E, Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<E, Object>
}
//... ...
}
public class TreeSet<E> extends AbstractSet<E>
implements SortedSet<E>, Cloneable, Serializable {
private transient SortedMap<E, Object> m;
private transient Set<E> keySet;
private static final Object PRESENT = new Object();
public TreeSet(SortedMap<E, Object> m) {
this.m = m;
keySet = m.keySet();
}
//... ...
}
SynchronizedSet、SynchronizedSortedSet
在多线程环境下将Set作为共享数据时,HashSet、TreeSet的API不能保证线程安全,所以需要线程安全的Set实现类,而SynchronizedSet、SynchronizedSortedSet则是该环境下的产物。它们的内部实现都是锁定this或者一个共享的Object对象来保证线程安全。
static class SynchonizedSet<E>
extends SynchronizedCollection<E> implements Set<E> {
public boolean equals(Object obj) {
synchronized(mutex) {
return c.equals(obj);
}
}
//... ...
}
static class SynchronizedSortedSet<E>
extends SynchronizedSet<E> implements SortedSet<E> {
private SortedSet<E> ss;
public Comparator<E> comparator() {
synchronized(mutex) {
return ss.comparator();
}
}
}
CopyOnWriteArraySet
它是一个有序Set集合,根据存放元素的先后顺序来存放,而不同于TreeSet根据Comparator比较值决定顺序。此外,CopyOnWriteArrayList底层由System.copyarray来实现增add、删remove操作。对CopyOnWriteArraySet结构进行修改将增加内存消耗,因为它每次都是全量拷贝当前数组到新创建的数组,频繁的修改会造成内存中驻留过多的数组对象待回收。但它也保证了Set集合的修改不会抛出ConcurrentModificationException异常,也即对结构的修改不会彼此影响。
public class CopyOnWriteArraySet<E>
extends AbstractSet<E> implements Serializable {
private final CopyOnWriteArrayList<E> al;
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
}
结论
Set集合是无序、不重复和可扩展的Collection衍生类,它存放的是具有相同特性的一组对象。在实现方式上,HashSet、TreeSet都依赖于Map集合,而线程安全上SynchronizedSet、SynchronizedSortedSet都锁定内部对象mutex,在结构修改上保证操作的原子性,CopyOnWriteArraySet保证每次操作都是整个容器进行拷贝,避免fail-fast异常。