EnumSet源码解析

来源:
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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值