课程咨询 :186 8716 1620      qq:2066486918

昆明Java培训 > 达内新闻 > Java程序建议:枚举项的数量限制在64个以内
  • Java程序建议:枚举项的数量限制在64个以内

    发布:昆明Java培训      来源:达内新闻      时间:2016-09-28

  • 枚举项的数量为什么要限制在64个以内?昆明Java培训机构的老师今天就给大家解答一下原因。

    为了更好地使用枚举,Java提供了两个枚举集合:EnumSet和EnumMap,这两个集合使用的方法都比较简单,EnumSet表示其元素必须是某一枚举的枚举项,EnumMap表示Key值必须是某一枚举的枚举项,由于枚举类型的实例数量固定并且有限, 相对来说EnumSet和EnumMap的效率会比其它Set和Map要高。

    虽然EnumSet很好用,但是它有一个隐藏的特点,昆明Java培训机构的老师逐步分析。在项目中一般会把枚举用作常量定义,可能会定义非常多的枚举项,然后通过EnumSet访问、遍历,但它对不同的枚举数量有不同的处理方式。为了 行对比,我们定义两个枚举,一个数量等于64,一个是65(大于64即可,为什么是64而不是128,512呢,一会解释),代码如下:

    1 //普通枚举项,数量等于64

    2 enum Const{

    3    A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,

    4    AA,BB,CC,DD,EE,FF,GG,HH,II,JJ,KK,LL,MM,NN,OO,PP,QQ,RR,SS,TT,UU,VV,WW,XX,YY,ZZ,

    5    AAA,BBB,CCC,DDD,EEE,FFF,GGG,HHH,III,JJJ,KKK,LLL

    6 }

    7 //大枚举,数量超过64

    8 enum LargeConst{

    9    A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,

    10    AA,BB,CC,DD,EE,FF,GG,HH,II,JJ,KK,LL,MM,NN,OO,PP,QQ,RR,SS,TT,UU,VV,WW,XX,YY,ZZ,

    11    AAAA,BBBB,CCCC,DDDD,EEEE,FFFF,GGGG,HHHH,IIII,JJJJ,KKKK,LLLL,MMMM

    12 }

    Const的枚举项数量是64,LagrgeConst的枚举项数量是65,接下来我们希望把这两个枚举转换为EnumSet,然后判断一下它们的class类型是否相同,代码如下:

    1 public class Client89 {

    2    public static void main(String[] args) {

    3        EnumSet<Const> cs = EnumSet.allOf(Const.class);

    4        EnumSet<LargeConst> lcs = EnumSet.allOf(LargeConst.class);

    5        //打印出枚举数量

    6        System.out.println("Const的枚举数量:"+cs.size());

    7        System.out.println("LargeConst的枚举数量:"+lcs.size());

    8        //输出两个EnumSet的class

    9        System.out.println(cs.getClass());

    10        System.out.println(lcs.getClass());

    11    }

    12 }

    程序很简单,现在的问题是:cs和lcs的class类型是否相同?应该相同吧,都是EnumSet类的工厂方法allOf生成的EnumSet类,而且JDK API也没有提示EnumSet有子类。我们来看看输出结果:

    很遗憾,两者不相等。就差一个元素,两者就不相等了?确实如此,这也是我们重点关注枚举项数量的原因。先来看看Java是如何处理的,首先跟踪allOf方法,其源码如下:

    1 public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {

    2        //生成一个空EnumSet

    3        EnumSet<E> result = oneOf(elementType);

    4        //加入所有的枚举项

    5        result.addAll();

    6        return result;

    7    }

    allOf通过noneOf方法首先生成了一个EnumSet对象,然后把所有的枚举都加进去,问题可能就出在EnumSet的生成上了,我们来看看noneOf的源码:

    1  public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {

    2        //获得所有的枚举项

    3        Enum[] universe = getUniverse(elementType);

    4        if (universe == null)

    5            throw new ClassCastException(elementType + " not an enum");

    6        //枚举数量小于等于64

    7        if (universe.length <= 64)

    8            return new RegularEnumSet<>(elementType, universe);

    9        else

    10            //枚举数量大于64

    11            return new JumboEnumSet<>(elementType, universe);

    12    }

    看到这里,恍然大悟,Java原来是如此处理的:当枚举项数量小于等于64时,创建一个RegularEnumSet实例对象,大于64时则创建一个JumboEnumSet实例对象。

    为什么要如此处理呢?这还要看看这两个类之间的差异,首先看RegularEnumSet类,源码如下:

    1 class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {

    2    private static final long serialVersionUID = 3411599620347842686L;

    3    /**

    4     * Bit vector representation of this set. The 2^k bit indicates the

    5     * presence of universe[k] in this set.

    6     */

    7    //记录所有的枚举号,注意是long型

    8    private long elements = 0L;

    9   //构造函数

    10    RegularEnumSet(Class<E>elementType, Enum[] universe) {

    11        super(elementType, universe);

    12    }

    13

    14   //加入所有元素

    15    void addAll() {

    16        if (universe.length != 0)

    17            elements = -1L >>> -universe.length;

    18    }

    19    

    20   //其它代码略

    21 }

    我们知道枚举项的排序值ordinal是从0、1、2......依次递增的,没有重号,没有跳号,RegularEnumSet就是利用这一点把每个枚举项的ordinal映射到一个long类型的每个位置上的,注意看addAll方法的elements元素,它使用了无符号右移操作, 且操作数是负值,位移也是负值,这表示是负数(符号位是1)的"无符号左移":符号位为0,并补充低位,简单的说,Java把一个不多于64个枚举项映射到了一个long类型变量上。这才是EnumSet处理的重点,其他的size方法、contains方法 都是根据elements方法等都是根据elements计算出来的。想想看,一个long类型的数字包含了所有的枚举项,其效率和性能能肯定是非常优秀的。

    我们知道long类型是64位的,所以RegularEnumSet类型也就只能负责枚举项的数量不大于64的枚举(这也是我们以64来举例,而不以128,512举例的原因),大于64则由JumboEnumSet处理,我们看它是怎么处理的:

    1 class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {

    2    private static final long serialVersionUID = 334349849919042784L;

    3

    4    /**

    5     * Bit vector representation of this set. The ith bit of the jth

    6     * element of this array represents the presence of universe[64*j +i]

    7     * in this set.

    8     */

    9   //映射所有的枚举项

    10    private long elements[];

    11

    12    // Redundant - maintained for performance

    13    private int size = 0;

    14

    15    JumboEnumSet(Class<E>elementType, Enum[] universe) {

    16        super(elementType, universe);

    17        //默认长度是枚举项数量除以64再加1

    18        elements = new long[(universe.length + 63) >>> 6];

    19    }

    20

    21      void addAll() {

    22        //elements中每个元素表示64个枚举项

    23        for (int i = 0; i < elements.length; i++)

    24            elements[i] = -1;

    25        elements[elements.length - 1] >>>= -universe.length;

    26        size = universe.length;

    27    }

    28 }

    JumboEnumSet类把枚举项按照64个元素一组拆分成了多组,每组都映射到一个long类型的数字上,然后该数组再放置到elements数组中,简单来说JumboEnumSet类的原理与RegularEnumSet相似,只是JumboEnumSet使用了long数组容纳更多的枚举项。不过 ,这样的程序看着会不会觉得郁闷呢?其实这是因为我们在开发中很少使用位移操作。大家可以这样理解:RegularEnumSet是把每个枚举项映射到一个long类型数字的每个位上,JumboEnumSet是先按照64个一组进行拆分,然后每个组再映射 一个long类型数字的每个位上。

    从以上昆明Java培训机构的老师的分析可知,EnumSet提供的两个实现都是基本的数字类型操作,其性能肯定比其他的Set类型要好的多,特别是Enum的数量少于64的时候,那简直就是飞一般的速度。

    注意:枚举项数量不要超过64,否则建议拆分。

    推荐文章

上一篇:Java程序建议:用枚举实现工厂方法模式更简洁

下一篇:java程序建议:小心注解继承

最新开班日期  |  更多

Java--零基础全日制班

Java--零基础全日制班

开班日期:11/30

Java--零基础业余班

Java--零基础业余班

开班日期:11/30

Java--周末提升班

Java--周末提升班

开班日期:11/30

Java--零基础周末班

Java--零基础周末班

开班日期:11/30

  • 网址:http://km .java.tedu.cn      地址:昆明市官渡区春城路62号证券大厦附楼6楼
  • 课程培训电话:186 8716 1620      qq:2066486918    全国服务监督电话:400-827-0010
  • 服务邮箱 ts@tedu.cn
  • 2001-2016 达内国际公司(TARENA INTERNATIONAL,INC.) 版权所有 京ICP证08000853号-56