EnumMap

felix.shao2025-02-16

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 的逻辑。

附录一、参考文献

Last Updated 2/18/2025, 2:43:17 PM