016_java.util.EnumSet

继承体系

image.png
EnumSet是一个abstract类,真正实现有两个,分别是RegularEnumSet与JumboEnumSet。这两个类的区别在于传入的枚举类型其内的实例个数,如果枚举实例个数小于64则使用RegularEnumSet否则使用JumboEnumSet。

产生实例方法

由于numSet是一个abstract类不能进行初始化操作,而两个子类并没有使用public修饰,意味着我们也不能直接初始化其子类。设计上还是需要EnumSet进行初始化。参考下面的源码,可以看到EnumSet提供了一系列static的方法帮助我们初始化实例。这里的初始化static方法可以分为两套,一个是使用枚举类型产生,一个是传递EnumSet拷贝出一份EnumSet进行使用。

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);
}

public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
    EnumSet<E> result = noneOf(elementType);
    result.addAll();
    return result;
}

public static <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s) {
    return s.clone();
}

public static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c) {
    if (c instanceof EnumSet) {
        return ((EnumSet<E>)c).clone();
    } else {
        if (c.isEmpty())
            throw new IllegalArgumentException("Collection is empty");
        Iterator<E> i = c.iterator();
        E first = i.next();
        EnumSet<E> result = EnumSet.of(first);
        while (i.hasNext())
            result.add(i.next());
        return result;
    }
}

public static <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s) {
    EnumSet<E> result = copyOf(s);
    result.complement();
    return result;
}

public static <E extends Enum<E>> EnumSet<E> of(E e) {
    EnumSet<E> result = noneOf(e.getDeclaringClass());
    result.add(e);
    return result;
}


public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2) {
    EnumSet<E> result = noneOf(e1.getDeclaringClass());
    result.add(e1);
    result.add(e2);
    return result;
}


public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3) {
    EnumSet<E> result = noneOf(e1.getDeclaringClass());
    result.add(e1);
    result.add(e2);
    result.add(e3);
    return result;
}


public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4) {
    EnumSet<E> result = noneOf(e1.getDeclaringClass());
    result.add(e1);
    result.add(e2);
    result.add(e3);
    result.add(e4);
    return result;
}

public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4,
                                                E e5)
{
    EnumSet<E> result = noneOf(e1.getDeclaringClass());
    result.add(e1);
    result.add(e2);
    result.add(e3);
    result.add(e4);
    result.add(e5);
    return result;
}

@SafeVarargs
public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest) {
    EnumSet<E> result = noneOf(first.getDeclaringClass());
    result.add(first);
    for (E e : rest)
        result.add(e);
    return result;
}

public static <E extends Enum<E>> EnumSet<E> range(E from, E to) {
    if (from.compareTo(to) > 0)
        throw new IllegalArgumentException(from + " > " + to);
    EnumSet<E> result = noneOf(from.getDeclaringClass());
    result.addRange(from, to);
    return result;
}

RegularEnumSet源码解析

RegularEnumSet是用在枚举量小于64的场合的。除此之外set语义上仅关心是否存在目标元素,Enum又是较为固定的数据,因此在RegularEnumSet中仅使用一个long的字段,使用其上的位来表达enum是否存在。初始化为0,所有位上数据都是0,意味着没有任何元素。

private long elements = 0L;

添加元素方法

添加元素方法有4个,分别是,添加一个元素,添加范围元素,添加指定集合元素,添加默认全部:

public boolean add(E e) {
    typeCheck(e);

    long oldElements = elements;
    elements |= (1L << ((Enum<?>)e).ordinal());
    return elements != oldElements;
}

void addRange(E from, E to) {
    elements = (-1L >>>  (from.ordinal() - to.ordinal() - 1)) << from.ordinal();
}

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;
}

void addAll() {
    if (universe.length != 0)
        elements = -1L >>> -universe.length;
}

这里可以看到都是使用位运算进行处理,接下来我们将最核心的位运算处理单独拉出来:

  1. 下面代码表示将1的数据往左移动ordinal个单位然后或上原本数据,最终原本数据的ordinal位置的数据将被赋值为1
elements |= (1L << ((Enum<?>)e).ordinal())
  1. 下面的代码是将原本数据中的from-to之间的位置为1,其中from.ordinal() - to.ordinal() - 1是一个负值会产生这样的效果:1111 1111 >>> -2 = 0000 0011,在这个基础上再进行左移就会产生from-to之间的位被置为1的效果
(-1L >>>  (from.ordinal() - to.ordinal() - 1)) << from.ordinal();
  1. 下面这段代码是将枚举长度部分置为1其他为0的逻辑,与2一样,使用 -1 >>> 负值来控制
elements = -1L >>> -universe.length

删除元素方法

删除元素分为单个删除与批量删除:

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;
    // 1L进行左移,再取反,会出现64位除了目标位置都是1的效果
    // 0与任何位 进行 &运算都是0,逻辑上就是针对该位置进行置0操作,达到删除的效果
    elements &= ~(1L << ((Enum<?>)e).ordinal());
    return elements != oldElements;
}

public boolean removeAll(Collection<?> c) {
    if (!(c instanceof RegularEnumSet))
        return super.removeAll(c);

    RegularEnumSet<?> es = (RegularEnumSet<?>)c;
    if (es.elementType != elementType)
        return false;

    long oldElements = elements;
    // 与单个元素删除的逻辑类似
    // 使用目标容器的elements取反然后做&操作达到批量删除的效果
    elements &= ~es.elements;
    return elements != oldElements;
}

判断元素是否存在

set的核心方法,contains有两个,一个是单个判断,一个是批量判断:

public boolean contains(Object e) {
    if (e == null)
        return false;
    Class<?> eClass = e.getClass();
    if (eClass != elementType && eClass.getSuperclass() != elementType)
        return false;

    // 1左移目标单位,会形成只有该位置是1其他都是0的数据
    // 0 与任何数&运算都得到0,因此最终结果就看目标位置是否为1,如果为1则结果必然不为0
    return (elements & (1L << ((Enum<?>)e).ordinal())) != 0;
}

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;
}

JumboEnumSet

JumboEnumSet在枚举数量超过64个的时候产生效果,其类内就不能使用单个long来存储数据了,而是一个使用一个long的数组来存储。那么在这个类中相比RegularEnumSet就是需要多上一步确定是第几号位置的long来处理。这里仅举个例子,其他方法大多差不多。

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;
}

可以看到其中有一个步骤是向右位移6,实际上就是除以64的意思,获得到eWordNum来作为long数组的index。

总结

EnumSet是一个Abstract类不能实例化,他提供一系列的static方法获得其实现对象。实现类有两种,分别是分别是RegularEnumSet与JumboEnumSet,当枚举的个数小于64则使用RegularEnumSet,其内部使用一个字段long来存储数据,JumboEnumSet则使用long的数组进行存储数据。他们的操作基于位运算完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值