美文网首页
Java集合-EnumMap源码实现分析

Java集合-EnumMap源码实现分析

作者: Misout | 来源:发表于2017-08-30 18:18 被阅读411次

    概要

    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、元素的存储顺序按照枚举值的声明次序存储。

    相关文章

      网友评论

          本文标题:Java集合-EnumMap源码实现分析

          本文链接:https://www.haomeiwen.com/subject/ouqxjxtx.html