EnumMap
EnumMap
简述
核心内容
- 数据结构。
- 初始化。
- 添加元素。
- 移除元素。
- 查找。
- 扩容(含自动扩容)。
EnumMap 简介
EnumMap 是一个用于存储 key 为枚举类型的 map,底层使用数组实现(K,V 双数组)。
详述
数据结构和核心方法
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
implements java.io.Serializable, Cloneable{
/** key 类型 */
private final Class<K> keyType;
/** key 数组 */
private transient K[] keyUniverse;
/** value 数组 */
private transient Object[] vals;
private transient int size = 0;
public V put(K key, V value)...;
public void putAll(Map<? extends K, ? extends V> m)...;
public V get(Object key)...;
public V remove(Object key)...;
}
数据结构主要是 keyType、keyUniverse、vals、size。
与其他类型 map 不同的是 EnumMap 底层使用双数组来存储 key 与 value,key 数组会在构造函数中根据 keyType 进行初始化,下面我们会看到。当 EnmumMap 的 value 为 null 时会特殊处理为一个 Object 对象。
初始化
构造函数(初始化)
有三个构造方法,其中使用了 SharedSecrets 和 JavaLangAccess 来获取 JVM 中的对象实例。
public EnumMap(Class<K> keyType) {
this.keyType = keyType;
keyUniverse = getKeyUniverse(keyType);
vals = new Object[keyUniverse.length];
}
public EnumMap(EnumMap<K, ? extends V> m) {
keyType = m.keyType;
keyUniverse = m.keyUniverse;
vals = m.vals.clone();
size = m.size;
}
public EnumMap(Map<K, ? extends V> m) {
if (m instanceof EnumMap) {
EnumMap<K, ? extends V> em = (EnumMap<K, ? extends V>) 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");
keyType = m.keySet().iterator().next().getDeclaringClass();
keyUniverse = getKeyUniverse(keyType);
vals = new Object[keyUniverse.length];
putAll(m);
}
}
private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) {
return SharedSecrets.getJavaLangAccess()
.getEnumConstantsShared(keyType);
}
添加元素-put
public V put(K key, V value) {
// key 类型检查
typeCheck(key);
// 获得该 key 对应的位置
int index = key.ordinal();
// 在 vals 数组中获取 key 角标对应的 value
Object oldValue = vals[index];
// 覆盖或设置 value
vals[index] = maskNull(value);
// 如果 key 对应的位置 value 为 null,则表示新插入了键值对,size++,反之表示值覆盖 size 不变
if (oldValue == null)
size++;
return unmaskNull(oldValue);
}
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);
}
private V unmaskNull(Object value) {
return (V)(value == NULL ? null : value);
}
EnumMap 存储键值对时并不会根据 key 获取对应的哈希值,enum 本身已经提供了一个 ordinal()方法,该方法会返回具体枚举元素在枚举类中的位置(从 0 开始),因此一个枚举元素从创建就已经有了一个唯一索引与其对应,这样就不存在哈希冲突的问题了。
EnmuMap 添加键值对并没有扩容操作,因为一个枚举类型到底有多少元素在代码运行阶段是确定的,在构造函数中已经对 key 数组进行了初始化与赋值,value 数组的大小也已经被确定。还有一个需要注意的问题,在上面的 put 方法中只对 value 进行了处理,并没有处理 key,原因就是 key 数组在构造函数中已经被赋值了。
添加元素-putAll
public void putAll(Map<? extends K, ? extends V> m) {
if (m instanceof EnumMap) {
EnumMap<?, ?> em = (EnumMap<?, ?>)m;
if (em.keyType != keyType) {
if (em.isEmpty())
return;
throw new ClassCastException(em.keyType + " != " + keyType);
}
for (int i = 0; i < keyUniverse.length; i++) {
// 因为用的枚举,其 key 的 key 数组索引一致,因此可以这么操作
Object emValue = em.vals[i];
if (emValue != null) {
if (vals[i] == null)
size++;
vals[i] = emValue;
}
}
} else {
super.putAll(m);
}
}
查找元素-get
public V get(Object key) {
return (isValidKey(key) ?
unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
}
private boolean isValidKey(Object key) {
if (key == null)
return false;
// Cheaper than instanceof Enum followed by getDeclaringClass
Class<?> keyClass = key.getClass();
return keyClass == keyType || keyClass.getSuperclass() == keyType;
}
移除元素-remove
public V remove(Object key) { if (!isValidKey(key)) return null; int index = ((Enum<?>)key).ordinal(); Object oldValue = vals[index]; vals[index] = null; if (oldValue != null) size--; return unmaskNull(oldValue); }
总结
- 添加元素、查询、移除元素的时间复杂度是 O(1)。
- 没有使用 fail-fast,没有加锁,是非线程安全的。
TIP
有兴趣的可以看下<K,V>
双数组怎么实现类似 Map.Entry 及 Iterator 的逻辑。