Java集合-EnumMap源码实现分析

2017-08-30  本文已影响411人  Misout

概要

EnumMap是专门为枚举类型量身定做的Map实现。

示例

先来看个示例代码:

public class EnumTest {
    
    public enum Color {
        red, blue, black, yellow, green
    }
    
    public static void main(String[] args) {
        EnumMap<Color,String> map = new EnumMap<>(Color.class);
        map.put(Color.yellow, "黄色");
        map.put(Color.blue, "蓝色");
        map.put(Color.red, "红色");
        map.put(Color.black, "黑色");
        map.put(Color.green, "绿色");

        for(Map.Entry<Color,String> entry : map.entrySet()){
            System.out.println(entry.getKey()+":"+entry.getValue());
        }
        System.out.println(map);
    }
}

输出:

red:红色
blue:蓝色
black:黑色
yellow:黄色
green:绿色
{red=红色, blue=蓝色, black=黑色, yellow=黄色, green=绿色}

从输出来看,迭代的顺序与Color枚举类型下定义的值顺序一致。

EnumMap定义和数据结构

类定义如下:

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

    // 枚举类的class对象,例如示例的Color.class
    private final Class<K> keyType;
    
    // 存储key的数组,key即枚举类的值对象,包括了name和ordinal两个属性
    private transient K[] keyUniverse;
    
    // 存储value的数组,value允许null,null会被转换成Object NULL实例替代存储
    private transient Object[] vals;
    
    private transient int size = 0;
    
    // Object的实例,用于代表null,用于区分值数组中元素本身是null(还未存值),
    // 还是存储的就是null值(已经存值)
    private static final Object NULL = new Object() {
        public int hashCode() {
            return 0;
        }

        public String toString() {
            return "java.util.EnumMap.NULL";
        }
    };

    private Object maskNull(Object value) {
        return (value == null ? NULL : value);
    }

    @SuppressWarnings("unchecked")
    private V unmaskNull(Object value) {
        return (V)(value == NULL ? null : value);
    }
    
    // 其他省略
}

根据类定义,存储结构图如下:

EnumMap数据结构图

EnumMap采用两个独立的数组分别维护key和value。由于EnumMap的key必须为指定的枚举类的类型,而枚举类下的值数量和ordinal(声明次序)已经固定,因此数组的容量大小在构造方法中就已经固定了。key存在keyUniverse数组指定的下标中,value也存在vals相同的下标中。这样key和value就建立了逻辑上的关系,便于get和put。

如果存一个null值,如下,则null会做特殊处理,转成Object NULL实例后存储,这样是为了区分索引下标的元素是否已经映射,没有映射则为null,映射为null了则用NULL实例进行占位替换。

put(Color.blue, null)

基本操作

EnumMap的基本操作都比较快,都在常量时间内完成。

1、put方法

public V put(K key, V value) {
    typeCheck(key);

    int index = key.ordinal();
    Object oldValue = vals[index];
    vals[index] = maskNull(value);
    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);
}

步骤:
1、检查key的类型是否是枚举类的类型,否则抛出ClassCastException异常。
2、用key的ordinal(声明次序值)作为数组的索引下标。
3、将value存到数组key的下标里面。如果是null元素转换为NULL实例存储。
4、更新元素数量计数器size。

2、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;
}

步骤:
1、检查key的类型是否为相应枚举类的类型,key为null或者类型不匹配返回null。
2、用key的ordinal值作为数组的索引下标,查找元素并返回,如果为NULL实例,则转换为null后返回。


总结

EnumMap是专门为枚举类型量身定做的Map实现。虽然使用其它的Map实现(如HashMap)也能完成枚举类型实例到值得映射,但是使用EnumMap会更加高效:它只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以EnumMap使用数组来存放与枚举类型对应的值。这使得EnumMap的效率非常高。EnumMap在内部使用枚举类型的ordinal()得到当前实例的声明次序,并使用这个次序维护枚举类型实例对应值在数组的位置。

1、父类为AbstractMap,未实现Map接口,只实现了Cloneable和Serializable接口。
2、非线程安全,所有方法和操作都未加锁。
3、采用key数组和vals数组共同实现key和value的关联。
4、不允许null key,但允许null value。
5、null值会被转换为Object的NULL实例占位替换。
6、元素的存储顺序按照枚举值的声明次序存储。

上一篇下一篇

猜你喜欢

热点阅读