来源:
Java编程的逻辑
EnumSet
EnumSet的实现与EnumMap没有任何关系,而是用极为精简和高效的位向量实现的;
位向量是计算机程序中解决问题的一种常用方式
1 实现原理
1.1 内部组成
final Class<E> elementType;//类型信息
final Enum[] universe;//枚举类的所有枚举值
EnumSet自身没有记录元素个数的变量,也没有位向量,它们是子类维护的。
1.对于RegularEnumSet,它用一个long类型表示位向量,代码为:
private long elements = 0L;
它没有定义表示元素个数的变量,是实时计算出来的,计算的代码是:
public int size() {
return Long.bitCount(elements);
}
2.对于JumboEnumSet,它用一个long数组表示,有单独的size变量,代码为:
private long elements[];
private int size = 0;
1.2 构造方法
EnumSet是一个抽象类,不能直接new
提供了若干静态工厂方法,可以创建EnumSet类型的对象,比如:
//创建一个指定枚举类型的EnumSet,不含任何元素;
//创建的EnumSet对象的实际类型是EnumSet的子类
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
//初始化枚举类数组
//最终调用了枚举类型的values方法,values方法返回所有可能的枚举值
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
//EnumSet是一个抽象类,它没有定义使用的向量长度,它有两个子类,RegularEnumSet和JumboEnumSet。 //RegularEnumSet使用一个long类型的变量作为位向量,long类型的位长度是64;
//JumboEnumSet使用一个long类型的数组;
//如果枚举值个数小于等于64,则创建的就是RegularEnumSet;大于64的话就是JumboEnumSet。
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
// 初始集合包括指定枚举类型的所有枚举值
public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType)
// 初始集合包括枚举值中指定范围的元素
public static <E extends Enum<E>> EnumSet<E> range(E from, E to)
// 初始集合包括指定集合的补集
public static <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s)
// 初始集合包括参数中的所有元素
public static <E extends Enum<E>> EnumSet<E> of(E e)
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2)
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3)
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4)
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5)
public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest)
// 初始集合包括参数容器中的所有元素
public static <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s)
public static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c)
其他工厂方法基本都是先调用noneOf构造一个空的集合,然后再调用添加方法
RegularEnumSet和JumboEnumSet的构造方法为:
RegularEnumSet(Class<E>elementType, Enum[] universe) {
super(elementType, universe);
}
//JumboEnumSet根据元素个数分配足够长度的long数组。
JumboEnumSet(Class<E>elementType, Enum[] universe) {
super(elementType, universe);
elements = new long[(universe.length + 63) >>> 6];
}
它们都调用了父类EnumSet的构造方法,其代码为:
//给实例变量赋值
EnumSet(Class<E>elementType, Enum[] universe) {
this.elementType = elementType;
this.universe = universe;
}
1.3 添加元素
RegularEnumSet的add方法的代码为:
public boolean add(E e) {
typeCheck(e);
long oldElements = elements;
//(1L << ((Enum)e).ordinal())会将元素e对应的位设为1,与现有的位向量elements相或,表示添加e。
//从集合论的观点来看,这就是求集合的并集。
elements |= (1L << ((Enum)e).ordinal());
return elements != oldElements;
}
JumboEnumSet的add方法的代码为:
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;
}
与RegularEnumSet的add方法的区别是,它先找对应的数组位置;
eOrdinal >>> 6就是eOrdinal除以64,eWordNum就表示数组索引,有了索引之后,其他操作与RegularEnumSet就类似了
对于其他操作,JumboEnumSet的思路是类似的,主要算法与RegularEnumSet一样,主要是增加了寻找对应long位向量的操作,或者有一些循环处理,逻辑也都比较简单,接下来就只介绍RegularEnumSet的实现了。
RegularEnumSet的addAll方法的代码为:
//类型正确的话,就是按位或操作
public boolean addAll(Collection<? extends E> c) {
if (!(c instanceof RegularEnumSet))
return super.addAll(c);
RegularEnumSet es = (RegularEnumSet)c;
if (es.elementType != elementType) {
if (es.isEmpty())
return false;
else
throw new ClassCastException(
es.elementType + " != " + elementType);
}
long oldElements = elements;
elements |= es.elements;
return elements != oldElements;
}
1.4 删除元素
public boolean remove(Object e) {
if (e == null)
return false;
Class eClass = e.getClass();
if (eClass != elementType && eClass.getSuperclass() != elementType)
return false;
long oldElements = elements;
//~是取反,该代码将元素e对应的位设为了0,这样就完成了删除。
elements &= ~(1L << ((Enum)e).ordinal());
return elements != oldElements;
}
1.5 查看是否包含集合中的所有元素
public boolean containsAll(Collection<?> c) {
if (!(c instanceof RegularEnumSet))
return super.containsAll(c);
RegularEnumSet es = (RegularEnumSet)c;
if (es.elementType != elementType)
return es.isEmpty();
return (es.elements & ~elements) == 0;
}
1.6 只保留参数集合中有的元素(交集)
public boolean retainAll(Collection<?> c) {
if (!(c instanceof RegularEnumSet))
return super.retainAll(c);
RegularEnumSet<?> es = (RegularEnumSet<?>)c;
if (es.elementType != elementType) {
boolean changed = (elements != 0);
elements = 0;
return changed;
}
long oldElements = elements;
elements &= es.elements;
return elements != oldElements;
}
1.7 求补集
void complement() {
if (universe.length != 0) {
elements = ~elements;
//elements是64位的,当前枚举类可能没有用那么多位,取反后高位部分都变为了1,需要将超出universe.length的部分设为0
//-1L是64位全1的二进制,相当于elements &= -1L >>> (64-universe.length);
elements &= -1L >>> -universe.length; // Mask unused bits
}
}
如果universe.length为7,则-1L>>>(64-7)就是二进制的1111111,与elements相与,就会将超出universe.length部分的右边的57位都变为0