java集合-Set篇
JDK提供的集合类型主要分为四种类型:
- List:支持重复元素
- Set:不支持重复元素
- Map:键/值对的映射集
- Queue/Deque(double ended queue):queue是在集合尾部添加元素,在头部删除元素的队列,deque是可在头部和尾部添加或者删除元素的双端队列,deque既可以实现队列又可以实现栈。
本文基于JDK8,java version “1.8.0_251”
EnumSet(RegularEnumSet/JumboEnumSet)
枚举类的容器,非线程安全,有序集合
-
EnumSet是抽象类,使用静态方法noneOf构建,如果枚举类的值不超过64,则使用创建RegularEnumSet实例,如果超过64位,则创建的JumboEnumSet。
EnumSet.noneOf(E.class); public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { Enum<?>[] universe = getUniverse(elementType); if (universe == null) throw new ClassCastException(elementType + " not an enum"); if (universe.length <= 64) return new RegularEnumSet<>(elementType, universe); else return new JumboEnumSet<>(elementType, universe); }
-
有序集合,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序
-
RegularEnumSet内部通过Bit数组来存放枚举值,而这个Bit数组其实就是一个Long类型数值,初始时是0L,是一个容量为64的Bit数组(Long类型长度为8字节,一个字节等于8位)。添加元素时,是把对应枚举元素的ordinal(每一个枚举类的枚举值都对应一个ordinal值)值映射到64Bit上的某一个位置为1。
/** * Bit vector representation of this set. The 2^k bit indicates the * presence of universe[k] in this set. */ private long elements = 0L; /** * Adds the specified element to this set if it is not already present. * * @param e element to be added to this set * @return <tt>true</tt> if the set changed as a result of the call * * @throws NullPointerException if <tt>e</tt> is null */ public boolean add(E e) { typeCheck(e); long oldElements = elements; elements |= (1L << ((Enum<?>)e).ordinal()); return elements != oldElements; }
-
JumboEnumSet内部则通过long数组类存放枚举值
/** * Bit vector representation of this set. The ith bit of the jth * element of this array represents the presence of universe[64*j +i] * in this set. */ private long elements[]; /** * Adds the specified element to this set if it is not already present. * * @param e element to be added to this set * @return <tt>true</tt> if the set changed as a result of the call * * @throws NullPointerException if <tt>e</tt> is null */ public boolean add(E e) { typeCheck(e); int eOrdinal = e.ordinal(); int eWordNum = eOrdinal >>> 6; long oldElements = elements[eWordNum]; elements[eWordNum] |= (1L << eOrdinal); boolean result = (elements[eWordNum] != oldElements); if (result) size++; return result; }
-
非线程安全,可以通过**Collections.synchronizedSet()**方法把它转成线程安全的集合,即使性能不是很高,但是似乎是唯一的选择。
TreeSet
有序集合,非线程安全,不允许null元素(Treemap不允许null的key,因为要使用它的compareTo方法)
-
内部通过TreeMap来存储元素,把元素存储在TreeMap的key里,value为同一个Object实例,通过TreeMap存储Key的有序性和无重复性来实现自己的有序性和Set的的元素无重复性;插入元素时,会根据元素compareTo方法判断是否重复和顺序(所以TreeSet中的元素必须实现Comparable接口并重写compareTo方法),然后进行排序。
// Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); /** * Constructs a new, empty tree set, sorted according to the * natural ordering of its elements. All elements inserted into * the set must implement the {@link Comparable} interface. * Furthermore, all such elements must be <i>mutually * comparable</i>: {@code e1.compareTo(e2)} must not throw a * {@code ClassCastException} for any elements {@code e1} and * {@code e2} in the set. If the user attempts to add an element * to the set that violates this constraint (for example, the user * attempts to add a string element to a set whose elements are * integers), the {@code add} call will throw a * {@code ClassCastException}. */ public TreeSet() { this(new TreeMap<E,Object>()); }
-
非线程安全,可以通过**Collections.synchronizedSortedSet()**方法把它转成线程安全的集合
-
不支持fail-fast机制,用的是TreeMap的iterator,没有checkForComodification(),更不会抛出ConcurrentModificationException异常。
-
不允许null元素,会抛出空指针异常,Treemap不允许null的key,插入时要使用它的compareTo方法会造成空指针异常
-
有序集合,根据元素的CompareTo方法顺序或自定义的comparator比较顺序。
HashSet
无序集合,非线程安全,允许null元素
-
内部通过HashMap来存储元素,把元素存储在HashMap的key里,value为同一个Object实例,通过HashMap存储Key的无重复性来实现自己的无重复性,插入元素时,会根据元素的hashcode方法判断元素是否重复(key为null时,hashcode为0),包含一个基于LinkedHashMap的构造方法(给同一个包下的其他类使用,比如LinkedHashSet)。
// Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); } static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
-
非线程安全,可以通过**Collections.synchronizedSortedSet()**方法把它转成线程安全的集合
-
支持fail-fast机制,用的是HashMap的iterator,会判断modCount的值是否被修改,如果modCount != expectedModCount,则抛出ConcurrentModificationException异常。
-
允许null元素,因为HashMap允许null的key
-
无序集合
LinkedHashSet
有序集合,非线程安全,允许null元素
-
继承至HashSet,除了构造方法和重写的spliterator方法,没有任何多余的成员变量和其他的方法。
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable {
-
有序集合,内部通过LinkedHashMap来存储元素,构造方法中主要是调用父类HashSet的构造方法构建一个LinkedHashMap,相比LinkedHashMap可以控制按插入元素时的顺序还是按访问顺序进行排序,LinkedHashSet只能按插入元素的顺序进行排序。
-
非线程安全,可以通过**Collections.synchronizedSortedSet()**方法把它转成线程安全的集合。
-
支持fail-fast机制,nextNode方法中会判断modCount值,如果modCount != expectedModCount,则会抛出ConcurrentModificationException异常。
-
允许null元素,因为LinkedHashMap允许null的key
CopyOnWriteArraySet
无序集合,基于CopyOnWriteArrayList,线程安全,适合set大小一般很小且多读场景,内存占用大
-
基于CopyOnWriteArrayList实现,所有的操作都是通过CopyOnWriteArrayList对象进行的,构造方法中直接创建一个CopyOnWriteArrayList对象。
public class CopyOnWriteArraySet<E> extends AbstractSet<E> implements java.io.Serializable { private static final long serialVersionUID = 5457747651344034263L; private final CopyOnWriteArrayList<E> al; public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); }
-
添加元素前需要遍历整个数组,因为不允许重复,所以添加元素之前需要判断元素是否存在,CopyOnWriteArrayList额外提供了addIfAbsent()和addAllAbsent()这两个添加元素的API,通过这些API来添加元素时,只有当元素不存在时才执行添加操作。
-
线程安全。和CopyOnWriteArrayList一样,通过volatile和互斥锁来实现(因为直接使用了CopyOnWriteArrayList的方法)。
-
不支持fail-fast机制
ConcurrentSkipListSet
有序集合,基于ConcurrentSkipListMap,线程安全,适用于高并发场景
-
基于ConcurrentSkipListMap,把元素存储在ConcurrentSkipListMap的key里,value为常量
Boolean.TRUE
,通过ConcurrentSkipListMap存储Key的有序性和无重复性来实现自己的有序性和Set的的元素无重复性。public class ConcurrentSkipListSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable { private final ConcurrentNavigableMap<E,Object> m;//实际上ConcurrentSkipListMap的实例 public ConcurrentSkipListSet() { m = new ConcurrentSkipListMap<E,Object>(); }
-
线程安全,因为ConcurrentSkipListMap是线程安全的。
-
不支持fail-fast机制,用的是ConcurrentSkipListMap的KeyIterator。
-
不允许null元素,会抛出空指针异常。ConcurrentSkipListMap的key和value都不支持null。
-
有序集合,根据元素的CompareTo方法顺序或自定义的comparator比较顺序。