EnumSet原理介绍

本文深入探讨了EnumSet数据结构的设计理念与应用场景,特别是在用户身份管理中的高效位运算操作,对比了RegularEnumSet与JumboEnumSet的实现差异,解析了EnumSet如何通过位操作节省空间并提升性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为了便于理解EnumSet,需要先知道这种数据结构是为了解决什么问题而设计的。

一、场景

举例一个场景,你系统里的用户会有各种身份,你设计了一个UserDTO对象,这时候要考虑怎么来表示身份,有一种比较常用的方法,比如定义一个Long属性,long类型有64位,每一位对应一种身份,1标识拥有该身份,0标识没有该身份。这样做就比较节省空间,那么在对这个UserDTO增加身份、删除身份、判断身份时,都需要自己做位运算,比较繁琐并且容易出错,而EnumSet就是为了解决这种场景来设计的,比如在UserDTO里设计一个EnumSet类型的属性来标识身份,再定义一个枚举类来标识身份,代码如下:

public class UserDTO {
   private Long userId;
   private String name;
   /**
    * 身份
    */
   private EnumSet<IdentityEnu> identities;
   
   // 身份枚举
   enum IdentityEnu{
       TEACHER,DRIVER,WIFE,MAN,RICHER,VIP;
   }
}  

这时你对UserDTO的身份的新增可以用EnumSet的add,删除可以用remove,判断可以用contain,不用自己操作位运算!这是实际解决的场景,在脑海中有了大概的认识后再看下源码。

二、源码解析

主要也是从构造方法、add、remove、contain这几个主要方法分析下源码。

构造方法:

EnumSet使用静态工厂来代替了公共的构造方法
在这里插入图片描述
这里根据参数个数不同定义了多个,拿其中一个来看下就可以了。代码如下:

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

这里getUniverse方法的含义是判断下你传入的枚举里面定义了多少个枚举值,比如我的代码里的IdentityEnu 里定义了6个,那么这时候返回的就是RegularEnumSet这个子类,这里有两个疑问点:
1、为什么和64比较
2、为什么返回的是两个子类,有什么区别?
带着这两个疑问进到这两个子类源码里看下
在这里插入图片描述
在这里插入图片描述
截图里狂欢粗了两个最主要的区别,RegularEnumSet使用的是一个long属性来存储,而JumboEnumSet是用一个long的数组来存储,这也是为什么和64做比较的原因,因为long只有64位,最多标识64个枚举值,多的只能用数组来存储,这里解释了上面的两个问题,下面再看下是怎么来存储的?
这个可以从add、remove方法的源码里找答案,以RegularEnumSet为例:

add方法
 /**
     * 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;
        // 通过传入的Enum值得下标来做位运算
        elements |= (1L << ((Enum<?>)e).ordinal());
        return elements != oldElements;
    }  

这里可以看到add操作实际是用位运算,将这个long值对应你传入的枚举值的下标的那个bit位改成1,比如我传入的是IdentityEnu.DRIVER, 对应的ordinal是1,则是将elements的bit位中的第2位改成1,对于位运算符看不懂的同学可以自己查下就能理解了,看到这里应该能明白EnumSet的原理及使用场景了,而对于remove和contain就不用详细解释了。好处就是只用long就可以标识64中状态,节省空间,并且通过位操作来做处理,性能也很快。

题外思考:

为什么EnumSet提供的静态工厂类返回的实际是两个子类RegularEnumSet和JumboEnumSet,并且这两个类是protected的?
这两个子类只是对于EnumSet的接口的实现方式不同,不同的地方就是根据调用方的Enum的值得个数来决定的,对于调用方来说是不需要关心这些细节的,所以这两个子类只需要是protected就可以,并且通过静态工厂类返回给调用方,以后如果有更牛逼的算法出现时,只需要再扩展出一个子类就可以了,而调用方是无感知的,比较符合开闭原则。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值