目录
一、EnumMap
1、定义
2、put / putAll / get
3、remove / clear
二、EnumSet
1、定义
2、noneOf / allOf / of / range
3、copyOf / complementOf
三、RegularEnumSet
1、add / addAll / addRange / complement
2、 remove / removeAll / clear / retainAll
3、size / contains / containsAll / iterator
四、JumboEnumSet
1、add / addAll / addRange / complement
2、 remove / removeAll / clear / retainAll
3、size / contains / containsAll / iterator
EnumMap和EnumSet 都是为key类型为枚举的定制集合类,相比直接使用HashMap或者HashSet效率更高,更节省内存占用,本篇博客就详细探讨其实现细节。HashSet的实现是基于HashMap,HashMap的实现可以参考《java8 HashMap接口实现源码解析》。
EnumMap的类继承关系如下:
其包含的属性如下:
/**
* K的类型
*/
private final Class keyType;
/**
* 保存K值的数组,根据枚举类型来初始化,初始完成后不会改变,删除某个key时只删除对应的value,key值对应的数组元素不变
*/
private transient K[] keyUniverse;
/**
* 保存V值的数组,与保存K值的数组是一一对应的,可以通过枚举值的索引即ordinal属性来访问,该属性从0开始
*/
private transient Object[] vals;
/**
* Map的元素个数
*/
private transient int size = 0;
/**
* 表示值为null的value
*/
private static final Object NULL = new Object() {
public int hashCode() {
return 0;
}
public String toString() {
return "java.util.EnumMap.NULL";
}
};
要求K必须扩展自Enum,即必须是枚举类型,参考EnumMap的定义,如下:
其构造方法的实现如下:
public EnumMap(Class keyType) {
this.keyType = keyType;
//根据枚举类型获取所有的枚举值
keyUniverse = getKeyUniverse(keyType);
//创建一个跟枚举值数组长度一样的value数组
vals = new Object[keyUniverse.length];
}
public EnumMap(EnumMap m) {
//直接赋值
keyType = m.keyType;
keyUniverse = m.keyUniverse;
vals = m.vals.clone();
size = m.size;
}
public EnumMap(Map m) {
if (m instanceof EnumMap) {
//直接赋值
EnumMap em = (EnumMap) m;
keyType = em.keyType;
keyUniverse = em.keyUniverse;
vals = em.vals.clone();
size = em.size;
} else {
if (m.isEmpty())
throw new IllegalArgumentException("Specified map is empty");
//获取key值的类型
keyType = m.keySet().iterator().next().getDeclaringClass();
//根据枚举类型获取所有的枚举值
keyUniverse = getKeyUniverse(keyType);
vals = new Object[keyUniverse.length];
putAll(m);
}
}
//返回某个枚举类的所有枚举值,相当于调用对应枚举类的values方法
private static > K[] getKeyUniverse(Class keyType) {
return SharedSecrets.getJavaLangAccess()
.getEnumConstantsShared(keyType);
}
public V put(K key, V value) {
//检查key的类型是否指定的枚举类型keyType
typeCheck(key);
//获取枚举值的索引
int index = key.ordinal();
//获取该索引对应的V
Object oldValue = vals[index];
//赋值
vals[index] = maskNull(value);
if (oldValue == null) //等于null,说明没有这个key值,size加1
size++;
return unmaskNull(oldValue); //返回原来的值
}
public void putAll(Map extends K, ? extends V> m) {
if (m instanceof EnumMap) {
//如果是EnumMap
EnumMap, ?> em = (EnumMap, ?>)m;
if (em.keyType != keyType) {
if (em.isEmpty())
return;
//key类型不一致,抛出异常
throw new ClassCastException(em.keyType + " != " + keyType);
}
//遍历所有的key值
for (int i = 0; i < keyUniverse.length; i++) {
//获取m中对应key的value
Object emValue = em.vals[i];
if (emValue != null) {
if (vals[i] == null) //等于null,说明没有这个key值,size加1
size++;
vals[i] = emValue; //赋值
}
}
} else {
//m是普通的HashMap,遍历键值对,调用put方法
super.putAll(m);
}
}
public V get(Object key) {
//如果是有效的key值,则获取对应key值的索引对应的value
return (isValidKey(key) ?
unmaskNull(vals[((Enum>)key).ordinal()]) : null);
}
private void typeCheck(K key) {
Class> keyClass = key.getClass();
if (keyClass != keyType && keyClass.getSuperclass() != keyType)
throw new ClassCastException(keyClass + " != " + keyType);
}
private Object maskNull(Object value) {
return (value == null ? NULL : value);
}
@SuppressWarnings("unchecked")
private V unmaskNull(Object value) {
return (V)(value == NULL ? null : value);
}
//是否有效key值
private boolean isValidKey(Object key) {
if (key == null) //key不能为null
return false;
//判断key值的类型是否是keyType
Class> keyClass = key.getClass();
return keyClass == keyType || keyClass.getSuperclass() == keyType;
}
public V remove(Object key) {
if (!isValidKey(key)) //不是有效key,返回null
return null;
//获取key值索引对应的value
int index = ((Enum>)key).ordinal();
Object oldValue = vals[index];
vals[index] = null; //置为null
if (oldValue != null) //原来不为null,说明原来有这个key,则将size减1
size--;
return unmaskNull(oldValue);
}
public void clear() {
//将vals置为null,注意保存key值的数组keyUniverse没有变更
Arrays.fill(vals, null);
size = 0;
}
EnumSet的类继承关系如下:
注意EnumSet是一个抽象类,不能直接使用,该类有两个子类,如下:
这两个子类都是非public的,只能包内访问。EnumSet.noneOf 方法创建EnumSet实例时会使用这两个子类,其实现如下:
即枚举值的个数小于等于64时使用RegularEnumSet,大于64时使用JumboEnumSet。该类包含的属性如下:
/**
* 枚举的类型
*/
final Class elementType;
/**
* 枚举类E的所有枚举值
*/
final Enum>[] universe;
EnumSet定义了多个静态的工具类方法,其中核心方法如add的实现都留给了子类,下面逐一说明想法方法的实现。
这些方法都是用来创建EnumSet的,noneOf返回一个空的EnumSet,allOf返回一个包含指定枚举类所有枚举值的EnumSet,of方法返回一个包含一个或者多个的指定枚举值的EnumSet,range返回一个包含指定范围的枚举值的EnumSet。
//返回一个空的EnumSet
public static > EnumSet noneOf(Class 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);
}
//返回一个包含了所有枚举值的EnumSet
public static > EnumSet allOf(Class elementType) {
EnumSet result = noneOf(elementType);
//子类实现
result.addAll();
return result;
}
//创建只包含一个指定枚举值的EnumSet,有多个重载版本,枚举值从1个到5个
public static > EnumSet of(E e) {
EnumSet result = noneOf(e.getDeclaringClass());
//子类实现
result.add(e);
return result;
}
@SafeVarargs
public static > EnumSet of(E first, E... rest) {
EnumSet result = noneOf(first.getDeclaringClass());
result.add(first);
//rest是不定数量的数组,将其中的元素都添加到Set中
for (E e : rest)
result.add(e);
return result;
}
//创建包含指定范围内的枚举值的EnumSet
public static > EnumSet range(E from, E to) {
if (from.compareTo(to) > 0)
throw new IllegalArgumentException(from + " > " + to);
EnumSet result = noneOf(from.getDeclaringClass());
//子类实现
result.addRange(from, to);
return result;
}
//返回指定枚举类的所有枚举值
private static > E[] getUniverse(Class elementType) {
return SharedSecrets.getJavaLangAccess()
.getEnumConstantsShared(elementType);
}
测试用例如下:
enum Name{
SHL,
ABC,
DEF,
AVC,
DNG
}
@Test
public void test2() throws Exception {
//返回一个空的EnumSet
EnumSet names=EnumSet.noneOf(Name.class);
System.out.println(names);
//返回一个包含了所有枚举值的EnumSet
names=EnumSet.allOf(Name.class);
System.out.println(names);
//返回一个包含了指定枚举值的EnumSet
names=EnumSet.of(Name.ABC,Name.DEF);
System.out.println(names);
//返回一个包含了指定范围的枚举值的EnumSet,起始值都包含
names=EnumSet.range(Name.ABC,Name.AVC);
System.out.println(names);
}
}
输出如下:
copyOf用于复制指定的EnumSet,complementOf返回原EnumSet中不包含的枚举值。
public static > EnumSet copyOf(EnumSet s) {
return s.clone();
}
public static > EnumSet copyOf(Collection c) {
if (c instanceof EnumSet) {
//如果是EnumSet
return ((EnumSet)c).clone();
} else {
if (c.isEmpty())
throw new IllegalArgumentException("Collection is empty");
//如果不是EnumSet,则遍历其中的元素,逐一添加到EnumSet中
Iterator i = c.iterator();
E first = i.next();
EnumSet result = EnumSet.of(first);
while (i.hasNext())
result.add(i.next());
return result;
}
}
//返回的EnumSet中包含了所有的枚举值
public static > EnumSet complementOf(EnumSet s) {
EnumSet result = copyOf(s);
//complement会补齐所有s中不包含的枚举值,由子类实现
result.complement();
return result;
}
public EnumSet clone() {
try {
//调用Object的clone方法
return (EnumSet) super.clone();
} catch(CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
测试用例如下:
@Test
public void test3() throws Exception {
EnumSet names=EnumSet.allOf(Name.class);
System.out.println(names);
EnumSet names2=EnumSet.copyOf(names);
System.out.println(names2);
names.remove(Name.SHL);
names2.remove(Name.DNG);
System.out.println(names);
System.out.println(names2);
EnumSet names3=EnumSet.complementOf(names);
System.out.println(names3);
}
其输出如下:
RegularEnumSet适用于枚举值个数小于等于64的EnumSet,RegularEnumSet定义了一个私有的long类型变量elements,long类型一共64位,如果某一位为1则表示该位对应的值对应的枚举值已经添加到RegularEnumSet中了,具体细节参考下面的源码分析。
RegularEnumSet(ClasselementType, Enum>[] universe) {
super(elementType, universe);
}
public boolean add(E e) {
//检查e的枚举类型是否指定的类型
typeCheck(e);
//获取原来的elements值
long oldElements = elements;
//将ordinal对应的位标识为1,表示添加了对应元素
elements |= (1L << ((Enum>)e).ordinal());
//如果不等说明原来不存在e,返回true
return elements != oldElements;
}
void addAll() {
if (universe.length != 0) //将所有枚举值对应的位都标识为1
//-1实际就是64个1,注意此处是右移一个负值,实际是右移该负值的补码的后6位对应的位数,因为超过6位就大于64了
//以universe.length为8为例,-8的补码的后6位是111000,该值为56,64个1右移56位的结果就是8个1
elements = -1L >>> -universe.length;
}
public boolean addAll(Collection extends E> c) {
if (!(c instanceof RegularEnumSet))
//如果不是RegularEnumSet类型,则通过父类的addAll方法添加,底层是遍历c中的元素,循环调用add方法
return super.addAll(c);
//如果是RegularEnumSet类型
RegularEnumSet> es = (RegularEnumSet>)c;
if (es.elementType != elementType) {
if (es.isEmpty())
return false;
else
//枚举类型不符,抛出异常
throw new ClassCastException(
es.elementType + " != " + elementType);
}
long oldElements = elements;
//直接或运算,有一个为1则结果为1
elements |= es.elements;
//两者不等说明有添加新的元素了
return elements != oldElements;
}
void addRange(E from, E to) {
//将指定范围内的位标记为1,注意此处from.ordinal() - to.ordinal()是一个负值,再减1是为了将to也包含进来
elements = (-1L >>> (from.ordinal() - to.ordinal() - 1)) << from.ordinal();
}
void complement() {
if (universe.length != 0) {
//取非,原来为1的变成0了,原来为0的变成1了
elements = ~elements;
//将universe范围以外的1都置为0
elements &= -1L >>> -universe.length; // Mask unused bits
}
}
public boolean remove(Object e) {
if (e == null)
return false;
Class> eClass = e.getClass();
//不是指定枚举类,返回false
if (eClass != elementType && eClass.getSuperclass() != elementType)
return false;
long oldElements = elements;
//将ordinal对应的位置为0
elements &= ~(1L << ((Enum>)e).ordinal());
//如果不等于说明该元素原来是存在的,返回true
return elements != oldElements;
}
public boolean removeAll(Collection> c) {
if (!(c instanceof RegularEnumSet))
//调用父类removeAll,底层遍历c,循环调用remove方法
return super.removeAll(c);
RegularEnumSet> es = (RegularEnumSet>)c;
if (es.elementType != elementType)
return false; //枚举类型不符
long oldElements = elements;
//先对es.elements求非,原来的1变成0,再求且,就会将elements中同样是1的位置为0了
elements &= ~es.elements;
//如果不等于说明有元素被删除了
return elements != oldElements;
}
public void clear() {
//置为0,所有为1的位都置为0了
elements = 0;
}
//retainAll求两者的交集,将不在c中的元素移除,返回true表示有元素被移除了
public boolean retainAll(Collection> c) {
if (!(c instanceof RegularEnumSet))
//调用父类retainAll,遍历当前集合Set,如果不在c中则移除
return super.retainAll(c);
RegularEnumSet> es = (RegularEnumSet>)c;
if (es.elementType != elementType) {
//枚举类型不符,将当前元素全部清空
//不等于0说明原来的RegularEnumSet非空,此处清除了,发生修改了,返回true
boolean changed = (elements != 0);
elements = 0;
return changed;
}
//枚举类型一致
long oldElements = elements;
//两者求且,如果某个元素在elements存在而在es.elements中不存在则将elements中对应位置为0,实现删除的效果
elements &= es.elements;
return elements != oldElements;
}
public int size() {
//统计位为1的位的总数,比如bitCount(8)返回1,bitCount(3)返回2
return Long.bitCount(elements);
}
public boolean contains(Object e) {
if (e == null)
return false;
Class> eClass = e.getClass();
if (eClass != elementType && eClass.getSuperclass() != elementType)
return false; //枚举类型不符,返回false
//判断ordinal对应的位是否为0,为0表示不存在
return (elements & (1L << ((Enum>)e).ordinal())) != 0;
}
public boolean containsAll(Collection> c) {
if (!(c instanceof RegularEnumSet))
//调用父类的containsAll,遍历c中的元素,如果有一个在当前Set中不存在则返回false
return super.containsAll(c);
RegularEnumSet> es = (RegularEnumSet>)c;
if (es.elementType != elementType) //枚举类型不符,如果c是空的则返回true
return es.isEmpty();
//elements求反后跟 es.elements 求且,如果结果为0,说明c中的元素都在当前Set中,返回true
return (es.elements & ~elements) == 0;
}
public Iterator iterator() {
return new EnumSetIterator<>();
}
private class EnumSetIterator> implements Iterator {
/**
* 当前elements
*/
long unseen;
/**
* 上一次返回的枚举值对应位为1的值,比如上一次返回的枚举值的ordinal为4,则lastReturned的后8位为00010000
*/
long lastReturned = 0;
EnumSetIterator() {
unseen = elements;
}
public boolean hasNext() {
return unseen != 0;
}
@SuppressWarnings("unchecked")
public E next() {
if (unseen == 0)
throw new NoSuchElementException();
//unseen是奇数时,求且为1,是偶数时,求且是一个2的整数次幂
lastReturned = unseen & -unseen;
//遍历一个位对应的枚举值,就通过减法,将对应位置为0
unseen -= lastReturned;
return (E) universe[Long.numberOfTrailingZeros(lastReturned)];
}
public void remove() {
if (lastReturned == 0)
throw new IllegalStateException();
//将对应的位置为0
elements &= ~lastReturned;
lastReturned = 0;
}
}
JumboEnumSet适用于枚举值个数大于64个的枚举类,其底层实现跟RegularEnumSet一样都是根据位是否为1来判断该枚举值是否添加到了Set中,不过因为枚举值个数大于64个,无法用64位的long类型来记录所有的枚举值,所以将RegularEnumSet中long类型的elements改成了一个long类型数组,添加某个枚举值时,先将某个枚举值的ordinal属性除以64,算出该枚举值所属的elements数组索引,再用ordinal属性对64求余,将对应的位标识位1,详情参考下面的源码分析。
JumboEnumSet(ClasselementType, Enum>[] universe) {
super(elementType, universe);
//加上63的目的是为了对64整除后算出来的值能包含原来的
//以70为例,64整除的结果是1,1*64 小于原来的70,如果加上63,则整除的结果是2,大于原来的70
elements = new long[(universe.length + 63) >>> 6];
}
public boolean add(E e) {
//检查e的枚举类型是否合法
typeCheck(e);
int eOrdinal = e.ordinal();
//左移6位就是除以64,算出该枚举所属的elements数组索引
int eWordNum = eOrdinal >>> 6;
//获取原来的值
long oldElements = elements[eWordNum];
//eOrdinal的值是大于64的,此处右移,实际移动的位数是eOrdinal对64求余的结果
//比如eOrdinal的值是68,1L<<68的结果就是1<<4,10000
elements[eWordNum] |= (1L << eOrdinal);
//判断原elements数组索引的元素是否发生改变,如果改变则说明添加的枚举值原来不存在
boolean result = (elements[eWordNum] != oldElements);
if (result) //为true说明添加了一个新元素,size加1
size++;
return result;
}
void addAll() {
//遍历所有的数组元素,将值置为-1,-1时所有位都是1
for (int i = 0; i < elements.length; i++)
elements[i] = -1;
//最后一个数组元素不是所有位都是1的,只有universe.length对64求余的结果对应的位置为1,所以需要右移将其他的位置为0
//以70为例,对64求余的结果就是6,-70的补码的后6位是111010,即58,64个1右移58后还剩6个1
elements[elements.length - 1] >>>= -universe.length;
size = universe.length;
}
void addRange(E from, E to) {
int fromIndex = from.ordinal() >>> 6;
int toIndex = to.ordinal() >>> 6;
//如果起始枚举值位于同一个数组元素中
if (fromIndex == toIndex) {
elements[fromIndex] = (-1L >>> (from.ordinal() - to.ordinal() - 1))
<< from.ordinal();
} else {
//起始枚举值不在同一个数组元素中
//此处是左移,将低于from的位都置为0
elements[fromIndex] = (-1L << from.ordinal());
//将fromIndex + 1到toIndex-1之间的数组元素都置为-1,所有位变成1
for (int i = fromIndex + 1; i < toIndex; i++)
elements[i] = -1;
//将to之前的低位都置为1
elements[toIndex] = -1L >>> (63 - to.ordinal());
}
//注意调用addRange时,JumboEnumSet是空的,所以此处直接赋值size
size = to.ordinal() - from.ordinal() + 1;
}
void complement() {
//遍历所有数组元素,取非,原来为0的位变成1,原来为1的位变成0
for (int i = 0; i < elements.length; i++)
elements[i] = ~elements[i];
//处理最后一个数组元素,将多余的位都置为0
elements[elements.length - 1] &= (-1L >>> -universe.length);
//总长度减去原来的长度
size = universe.length - size;
}
public boolean remove(Object e) {
if (e == null)
return false;
Class> eClass = e.getClass();
if (eClass != elementType && eClass.getSuperclass() != elementType)
return false; //元素类型不符,返回false
//对64整除,算出所属的数组索引
int eOrdinal = ((Enum>)e).ordinal();
int eWordNum = eOrdinal >>> 6;
//将对应的数组元素的对应位置为0
long oldElements = elements[eWordNum];
elements[eWordNum] &= ~(1L << eOrdinal);
boolean result = (elements[eWordNum] != oldElements);
if (result) //如果改变说明原来是有这个枚举值的,size减1
size--;
return result;
}
public boolean removeAll(Collection> c) {
if (!(c instanceof JumboEnumSet))
//调用父类removeAll,底层是遍历c,调用remove方法
return super.removeAll(c);
JumboEnumSet> es = (JumboEnumSet>)c;
if (es.elementType != elementType)
return false; //元素类型不符,返回false
//遍历当前Set,先对es.elements[i]求非,将原来为1的位变成0,再和elements[i]求且,
//实现将位于es.elements[i]中的元素都从elements[i]中移除了
for (int i = 0; i < elements.length; i++)
elements[i] &= ~es.elements[i];
//重新计算size,返回size是否改变
return recalculateSize();
}
public boolean retainAll(Collection> c) {
if (!(c instanceof JumboEnumSet))
//调用父类retainAll,底层是遍历当前Set,如果不在c中则将其移除
return super.retainAll(c);
JumboEnumSet> es = (JumboEnumSet>)c;
if (es.elementType != elementType) {
//元素类型不一致,将当前Set清空
boolean changed = (size != 0);
clear();
return changed;
}
//遍历当前Set,两者的数组元素求且,不在es.elements[i]中的位就会被置为0,实现元素移除的效果
for (int i = 0; i < elements.length; i++)
elements[i] &= es.elements[i];
//重新计算size,返回size是否改变
return recalculateSize();
}
public void clear() {
//所有元素都置为0
Arrays.fill(elements, 0);
size = 0;
}
//重新计算size,并返回size是否发生改变
private boolean recalculateSize() {
int oldSize = size;
size = 0;
for (long elt : elements)
//遍历elements,累加位为1的总数
size += Long.bitCount(elt);
//如果size发生改变,说明有删除元素了,返回true
return size != oldSize;
}
public int size() {
return size;
}
public boolean contains(Object e) {
if (e == null)
return false;
Class> eClass = e.getClass();
if (eClass != elementType && eClass.getSuperclass() != elementType)
return false; //枚举类型不符,返回false
int eOrdinal = ((Enum>)e).ordinal();
//判断对应数组元素的对应位是否为0,如果是则说明不存在,返回false,否则返回true
return (elements[eOrdinal >>> 6] & (1L << eOrdinal)) != 0;
}
public boolean containsAll(Collection> c) {
if (!(c instanceof JumboEnumSet))
//调用父类containsAll,遍历c,如果有一个元素不在当前Set中,则返回false
return super.containsAll(c);
JumboEnumSet> es = (JumboEnumSet>)c;
if (es.elementType != elementType) //元素类型不符
return es.isEmpty();
for (int i = 0; i < elements.length; i++)
//elements[i]先求非,原来为1的位变成0,再求且,如果结果不为0,说明某个元素在
//elements[i]中不存在,在es.elements[i]中存在,即c中某个元素不在当前Set中,返回false
if ((es.elements[i] & ~elements[i]) != 0)
return false;
return true;
}
public Iterator iterator() {
return new EnumSetIterator<>();
}
private class EnumSetIterator> implements Iterator {
/**
* 当前遍历的elements元素
*/
long unseen;
/**
当前遍历的elements数组索引
*/
int unseenIndex = 0;
/**
* 上一次返回的为1的位
*/
long lastReturned = 0;
/**
* The index corresponding to lastReturned in the elements array.
*/
int lastReturnedIndex = 0;
EnumSetIterator() {
unseen = elements[0];
}
@Override
public boolean hasNext() {
//遍历找到下一个数组元素不为0的数组元素
while (unseen == 0 && unseenIndex < elements.length - 1)
unseen = elements[++unseenIndex];
return unseen != 0;
}
@Override
@SuppressWarnings("unchecked")
public E next() {
if (!hasNext())
throw new NoSuchElementException();
//逻辑同RegularEnumSet
lastReturned = unseen & -unseen;
lastReturnedIndex = unseenIndex;
unseen -= lastReturned;
return (E) universe[(lastReturnedIndex << 6)
+ Long.numberOfTrailingZeros(lastReturned)];
}
@Override
public void remove() {
if (lastReturned == 0)
throw new IllegalStateException();
final long oldElements = elements[lastReturnedIndex];
//对应的位置为0
elements[lastReturnedIndex] &= ~lastReturned;
if (oldElements != elements[lastReturnedIndex]) {
size--;
}
lastReturned = 0;
}
}