1. EnumSet
的概念
Set
接口的实现类HashSet/TreeSet
,它们内部都是用对应的HashMap/TreeMap
实现的,但EnumSet
不是EnumSet
的实现与EnumMap
没有任何关系,而是用极为精简和高效的位向量实现的
2. EnumSet
的基本用法
与
HashSet/TreeSet
不同,EnumSet
是一个抽象类,不能直接通过new
创建EnumSet
提供了若干静态工厂方法,可以创建EnumSet
类型的对象。比如:public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType);
Demo
1
2
3
4
5
6
7
8
9
10
11
12
13//表示星期几的枚举类 Day
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
//使用 noneOf() 方法
Set<Day> weekend = EnumSet.noneOf(Day.class);
weekend.add(Day.SATURDAY);
weekend.add(Day.SUNDAY);
System.out.println(weekend);
//输出
[SATURDAY, SUNDAY]EnumSet
有很多重载形式的of()
方法,表示初始集合包括参数中的所有元素。其中有接收可变参数的重载方法,其他重载方法看上去是多余的,之所以有其他重载方法是因为可变参数的运行效率低一些
3. 想象一个场景,在一些工作中(如医生、客服),不是每个工作人员每天都在的,每个人可工作的时间是不一样的。比如张三可能是周一和周三,李四可能是周四和周六。给定每个人可工作的时间,请用 EnumSet 实现下面几个场景需求(预定义代码如下所示)
1 | //表示星期几的枚举类 Day |
有哪些天一个人都不会来?
1
2
3
4
5
6
7
8
9
10
11//实际是在求 worker 时间并集的补集
Set<Day> days = EnumSet.allOf(Day.class);
for(Worker w : workers) {
days.removeAll(w.getAvailableDays());
}
System.out.println(days);
//输出
[SUNDAY]有哪些天至少会有一个人来?
1
2
3
4
5
6
7
8
9
10
11//实际是在求 worker 时间的并集
Set<Day> days = EnumSet.noneOf();
for(Worker w : workers) {
days.addAll(w.getAvailableDays());
}
System.out.println(days);
//输出
[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY]有哪些天所有人都会来?
1
2
3
4
5
6
7
8
9
10
11//实际是在求 worker 时间的交集
Set<Day> days = EnumSet.allOf(Day.class);
for(Worker w : workers) {
days.retainAll(w.getAvailableDays());
}
System.out.println(days);
//输出
[TUESDAY]哪些人周一和周二都会来?
1
2
3
4
5
6
7
8
9
10
11
12
13
14Set<Day> availableWorkers = new HashSet<Worker>();
for(Worker w : workers) {
if(w.getAvailableDays().containAll(EnumSet.of(Day.MONDAY, Day.TUESDAY))) {
availableWorders.add(w);
}
}
for(Worker w : availableWorkers) {
System.out.println(w.getName());
}
//输出
张三哪些天至少会有两个人来?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21Map<Day, Integer> countMap = new EnumMap<> (Day.class);
for(Worker w : workers) {
for(Day d : w.getAvailableDays()) {
Integer count = countMap.get(d);
countMap.put(d, count == null ? 1 : count+1);
}
}
Set<Day> days = EnumSet.noneOf(Day.class);
for(Map.Entry<Day, Integer> entry : countMap.entrySet()) {
if(entry.getValue() >= 2) {
days.add(entry.getKey());
}
}
System.out.println(days);
//输出
[TUESDAY, THURSDAY]
4. EnumSet
的实现原理
EnumSet
是使用位向量实现的,表示很简洁、节省空间,大部分操作都是按位运算、效率极高- 位向量:用一个位表示一个元素的状态,用一组位表示一个集合的状态,每个位对应一个元素,而状态只可能有两种
- 位向量能表示的元素个数与向量长度有关,一个
byte
类型能表示 8 个元素,一个long
类型能表示 64 个元素
EnumSet
是一个抽象类,它没有定义使用的向量长度- 它有两个子类:
RegularEnumSet
和JumboEnumSet
RegularEnumSet
使用一个long
类型的变量作为位向量,long
类型的位长度是 64JumboEnumSet
使用一个long
类型的数组
- 它有两个子类:
如果枚举值个数小于等于 64,则静态工厂方法中创建的就是
RegularEnumSet
;如果大于 64 就是JumboEnumSet
5. EnumSet
的内部组成
EnumSet
也有表示类型信息和所有枚举值的实例变量。如下1
2final Class<E> elementType; //表示类型信息
final Enum[] universe; //表示枚举类的所有枚举值EnumSet
自身没有记录元素个数的变量,也没有位向量,它们是子类维护的对于
RegularEnumSet
,它用一个long
类型表示位向量。代码为:private long elements = 0L;
。它没有定义表示元素个数的变量,是实时计算出来的,计算的代码是:public int size() { return Long.bitCount(elements); }
对于
JumboEnumSet
,它用一个long
数组表示,有单独的size
变量,代码为1
2private long elements[];
private int size = 0;
6. EnumSet
的特点
- 对于只有两种状态,且需要进行集合运算的数据,使用位向量进行表示、位运算进行处理。是计算机程序中一种常用的思维方式
- Java 中有一个更为通用的可动态扩展长度的位向量容器类
BitSet
,可以方便地对指定位置的位进行操作,与其他位向量进行位运算