数据结构 - EnumMap 类

简介

HashMap是一种通过空间换时间的方式,会造成空间浪费,而EnumMap内部是以紧凑的数组存储value,但是key必须是enum类型

EnumMap 类
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
    implements java.io.Serializable, Cloneable

EnumMap键必须继承Enum

属性
// key 类型
private final Class<K> keyType;
// key 数组
private transient K[] keyUniverse;
// value 数组
private transient Object[] vals;
// 键值对个数
private transient int size = 0;
// value 为 null 时对应的值
private static final Object NULL = new Object() {
    public int hashCode() {
        return 0;
    }
    public String toString() {
        return "java.util.EnumMap.NULL";
    }
}
构造函数
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);
    }
}

与其他类型 map 不同的是 EnumMap 底层使用双数组来存储 key 与 value,key 数组会在构造函数中根据 keyType 进行初始化,下面我们会看到。当 EnmumMap 的 value 为 null 时会特殊处理为一个 Object 对象,使用构造函数初始化 EnumMap 的时候必须指定枚举类型,上面我们已经说过,EnumMap 会在构造函数中初始化 key 数组,这个初始化动作是在 getKeyUniverse(keyType) 中完成的。

添加
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 开始),因此一个枚举元素从创建就已经有了一个唯一索引与其对应,这样就不存在哈希冲突的问题了。

删除
public V remove(Object key) {
    // key 类型错误的时候直接返回 null
    if (!isValidKey(key))
        return null;
    // 根据 key 计算出其在枚举中位置
    int index = ((Enum<?>)key).ordinal();
    // 获取对应的 value
    Object oldValue = vals[index];
    // value 置 null,下次 GC 回收
    vals[index] = null;
    // 如果对应的 value 不为 null,如果添加键值对的时候 value 为 null,则存储的是 NULL(Object)
    if (oldValue != null)
        size--;
    return unmaskNull(oldValue);
}
private boolean isValidKey(Object key) {
    // key 为 null 直接返回 false
    if (key == null)
        return false;
    // Cheaper than instanceof Enum followed by getDeclaringClass
    Class<?> keyClass = key.getClass();
    // key 类型检查
    return keyClass == keyType || keyClass.getSuperclass() == keyType;
}

在移除键值对的时候会先调用 isValidKey 方法对 key 进行检查

posted @ 2020-04-01 17:10  源码猎人  阅读(119)  评论(0编辑  收藏  举报