[疯狂Java]集合:EnumSet、各Set性能分析(选择)

时间:2021-07-05 11:55:48

1. EnumSet——专门存放枚举类型元素的Set:

    1) EnumSet只能存放一种枚举类型的元素,具体存放什么枚举类型的元素可以通过两种方法指定,一种是显式,一种是隐式;

    2) 一旦元素的枚举类型确定那么集合就确定了(即只能存放该种枚举类型的元素,不能同时存放多种枚举类型的元素!!否则就会抛出异常);

    3) 枚举集合EnumSet底层并不是直接存放枚举对象的,而是用二进制位向量来存放的,因此存储非常紧凑,并且高效(比如枚举类型A里有4个枚举值a、b、c、d,并且这4个值都是对象,现在一个枚举集合存放a、c、d三个值,由于枚举类型的所有值的个数是有限的,因此可以用二进制序列来唯一表示,这里就用4位二进制数表示,现在只有a、c、d这三个值,因此可以表示为1011,1表示该枚举值在集合中,0表示不在集合种;

!!枚举值在枚举类型中是有序号的,就是按照其定义顺序排列的;

!!而从枚举集合中取出一个枚举值是是根据二进制位的位置还原原本的枚举值的,比如取出第二个二进制位置的枚举值,那么根据枚举类型中枚举值定义的顺序,第二个位置是b,那么取出的就是b,即位置和值是一一对应的;

!!由于是二进制位操作,因此向containsAll、retainAll等方法会非常高效;

    4) EnumSet不允许包含null元素,强行添加会抛出异常!但是判空和删除null元素的方法可以正常调用,只不过永远返回null而已(因为不存在null元素,因此也没办法删除);

    5) 由于使用二进制位来保存的,重复就更加不用担心的,每个二进位的位置就代表一个枚举值,因此一定不会重复;


2. 构造EnumSet:

    1) EnumSet并没有提供公开的构造器来构造对象,而是提供了很多静态工具方法来构造EnumSet对象;

    2) 下面介绍的都是EnumSet的静态工具方法,用来构造EnumSet对象:共有显式和隐式两种

****显式:显式(手工)指出存放元素所属的枚举类型

         i. static EnumSet allOf(Class elementType);  // 将elementType所代表的枚举类型的所有枚举值加入到集合中,例如EnumSet es = EnumSet.allOf(Season.class);

!!将Season枚举类的所有枚举值SPRING、SUMMER、FALL、WINNTER加入到集合中

         ii. static EnumSet noneOf(Class elementType);  // 创建一个空的、只用来存放elementType枚举类型值的集合

****隐式:不直接在参数中指定枚举类型,而是通过参数的类型自动推断枚举类型

         i. 用其它枚举集合来构造:

            a. static EnumSet complementOf(EnumSet s);   // 用s之外的其它枚举值来构造一个集合(比如枚举类型有a、b、c、d 4个值,现在s有a和c,那么构造出来集合只包含b和d,即补集);

            b. static EnumSet copyOf(EnumSet s);  // s的深复制(不是引用复制)

         ii. 直接用枚举值来构造:

            a. static EnumSet<E> of(E first, E... rest);  // 直接用若干枚举值构建一个枚举集合,例如EnumSet es = EnumSet.of(Season.SPRING, Season.WINNTER);

            b. static EnumSet<E> range(E from, E to); // 直接用枚举值区间构建一个集合,包括[from, to]的所有枚举值

注意!

        *1. 直接用枚举值构造时枚举值必须都属于同一个类型,否则会报错!

        *2. [from, to]是闭区间!而不是左闭右开了,因为枚举类型无法表示最后一个值的后一个值!因此只有这里比较特殊,采用了右边闭合的区间;

        *3. 枚举值的顺序就是枚举类型中枚举值定义的顺序;

    3) 构造完之后就可以正常调用add等集合操作方法对枚举集合进行操作了;


3. 特殊的构造方式——用另一个集合来构造EnumSet:

    1) 原型:static EnumSet copyOf(Collection c);

    2) 如果c就是一个EnumSet那该方法就跟static EnumSet copyOf(EnumSet s);没有区别;

    3) 如果是c是普通的集合(Set、List等),那就要求c里面存放的必须是同一类型的枚举类型对象,因为它会将c中的全部元素加入到新集合中,如果类型不一致肯定是不行的;

!!注意两个关键点:

        i. c中元素的类型必须是枚举类型,否则和EnumSet的本质相违背肯定是会异常的!

        ii. c中的元素必须属于同一枚举类型,否则也会违反EnumSet类型一致的规定而异常的!

    4) 如果c是一个List,而里面的元素有重复会怎么样呢?没关系,重复元素就相当于add了多次,add的时候会自动判断是否重复的,如果重复则拒绝添加,因此最终产生的EnumSet是绝对不会重复(添加的时候自动去重了);

    5) 示例:

public class Test {

enum Season {
SPRING, SUMMER, FALL, WINNTER
}

public static void main(String[] args) {
List li = new ArrayList<>();
li.add(Season.SPRING);
li.add(Season.SPRING);
li.add(Season.SUMMER);

EnumSet es = EnumSet.copyOf(li);
System.out.println(es); // [SPRING, SUMMER]

}
}


4. Set各实现类的性能以及该如何选择使用哪种实现类:

    1) 就效率而言EnumSet毋庸置疑是最高的,毕竟是用二进制向量保存的,其次HashSet性能好于LinkedHashSet(仅仅多了一个维护插入顺序的链表),而TreeSet性能排最后,因为需要时刻维护红黑树的结构已达到有序状态(大小顺序);

    2) 至于碰到一个问题该选择何种Set的实现类就很简单了,关键看你是什么需求,枚举就用EnumSet,仅仅是一个无需集合就用HashSet,需要维护插入顺序就用LinkedHashSet,需要维护大小顺序的就只能用TreeSet了!