EnumMap源码阅读

EnumMap是一个用于存放键值为enum类型的map,所有的键值必须来自一个单一的enum类型。EnumMap内部用数组表示效率更高。

EnumMap维持键值的自然顺序(即枚举类型常量声明的顺序),可以通过keySet()entrySet()方法的集合视图来提现顺序。

集合视图返回的迭代器是弱一致的:遍历时候不会抛出ConcurrentModificationException,遍历过程中若对容器进行修改,修改产生的影响遍历过程可能可见也可能不可见。

不允许null键值插入,插入空值将会抛出NullPointerException。测试空值是否存在或删除空值操作是允许的。

和其他集合对象实现类似,EnumMap不保证线程安全,可以通过集合帮助类的方法进行包装,返回线程安全的容器。

Map<EnumKey, V> m = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));

EnumMap实现

类的定义,可以看到键值必须是继承Enum<K>对象的实例

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

成员变量

private final Class<K> keyType; //key对应的class对象
// All of the values comprising K.  (Cached for performance.)
private transient K[] keyUniverse;
//Array representation of this map.
private transient Object[] vals;
private transient int size = 0; //map的大小
//用非空值对象来表示null
private static final Object NULL = new Object();

构造方法

public EnumMap(Class<K> keyType) {
    this.keyType = keyType;
    keyUniverse = getKeyUniverse(keyType);
    vals = new Object[keyUniverse.length];
}
/**
 * Returns all of the values comprising K.
 * The result is uncloned, cached, and shared by all callers.
 * 调用sun.misc.SharedSecrets类中方法返回对应枚举类的成员
 */
private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) {
    return SharedSecrets.getJavaLangAccess()
                .getEnumConstantsShared(keyType);
}

put和remove方法

public V put(K key, V value) {
    typeCheck(key);
    int index = ((Enum)key).ordinal();
    Object oldValue = vals[index];
    vals[index] = maskNull(value);
    if (oldValue == null)
        size++;
    return unmaskNull(oldValue);
}
//检查是否为初始化时传入的枚举类型或其子类型(? extends K)
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);
}
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);
}

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

查找方法

public boolean containsKey(Object key) {
    return isValidKey(key) && vals[((Enum)key).ordinal()] != null;
}
//即使是value为null,由于内部调用maskNull所以不会有空指针异常
private boolean containsMapping(Object key, Object value) {
    return isValidKey(key) &&
        maskNull(value).equals(vals[((Enum)key).ordinal()]);
}
public boolean containsValue(Object value) {
    value = maskNull(value);
    for (Object val : vals)
        if (value.equals(val))
            return true;
    return false;
}

集合视图

HashMap类似,提供keySet()values()entrySet()方法,返回键集合、值集合、键值对集合视图。下面介绍下键集合视图,由于这些视图是无状态的,没必要每次都重新创建。ketSet方法返回一个内部类EnumMap$KeySet实例。

public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks != null)
        return ks;
    else
        return keySet = new KeySet();
}
private class KeySet extends AbstractSet<K> {
    public Iterator<K> iterator() {
        return new KeyIterator();
    }
    public int size() {
        return size;
    }
    public boolean contains(Object o) {
        return containsKey(o);
    }
    public boolean remove(Object o) {
        int oldSize = size;
        EnumMap.this.remove(o);
        return size != oldSize;
    }
    public void clear() {
        EnumMap.this.clear();
    }
}

视图的迭代器

KeySet的迭代器返回内部类KeyIterator实例,其继承自EnumMapIterator,实际EnumMapIterator实现了整个Entry的迭代,根据不同视图,next方法返回键、值或键值对。内部没有检查遍历过程是否修改,所以不会抛异常。

private class KeyIterator extends EnumMapIterator<K> {
    public K next() {
        if (!hasNext())
            throw new NoSuchElementException();
        lastReturnedIndex = index++;
        return keyUniverse[lastReturnedIndex];
    }
}

private class ValueIterator extends EnumMapIterator<V> {
    public V next() {
        if (!hasNext())
            throw new NoSuchElementException();
        lastReturnedIndex = index++;
        return unmaskNull(vals[lastReturnedIndex]);
    }
}

private abstract class EnumMapIterator<T> implements Iterator<T> {
    // Lower bound on index of next element to return
    int index = 0;
    // Index of last returned element, or -1 if none
    int lastReturnedIndex = -1;

    public boolean hasNext() {
        while (index < vals.length && vals[index] == null)
            index++;
        return index != vals.length;
    }

    public void remove() {
        checkLastReturnedIndex();
        if (vals[lastReturnedIndex] != null) {
            vals[lastReturnedIndex] = null;
            size--;
        }
        lastReturnedIndex = -1;
    }

    private void checkLastReturnedIndex() {
        if (lastReturnedIndex < 0)
            throw new IllegalStateException();
    }
}

总结

  1. EnumMap的键值必须是Enum类型,而且put的时候只能是初始化时指定的Enum或者其子类型。同时不支持键值为null。
  2. EnumMap初始化会创建存放key和value的两个数组,大小为Enum类型中成员数量,同时会缓存所有Enum类型到key数组。
  3. EnumMap迭代保持键值的自然顺序(即枚举类型常量声明的顺序),其实通过Enum内部ordinal()方法实现,vals数组每次插入元素都放插入到key值对应的ordinal()返回的位置。
时间: 2024-11-07 08:50:30

EnumMap源码阅读的相关文章

CI框架源码阅读笔记3 全局函数Common.php

从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap引导文件都会最先引入全局函数,以便于之后的处理工作). 打开Common.php中,第一行代码就非常诡异: if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 上一篇(CI框架源码阅读笔记2 一切的入口 index

淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划

body, td { font-family: tahoma; font-size: 10pt; } 淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划 SQL编译解析三部曲分为:构建语法树,生成逻辑计划,指定物理执行计划.第一步骤,在我的上一篇博客淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树里做了介绍,这篇博客主要研究第二步,生成逻辑计划. 一. 什么是逻辑计划?我们已经知道,语法树就是一个树状的结构组织,每个节点代表一种类型的语法含义.如

JDK部分源码阅读与理解

本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/05/31/JDK部分源码阅读与理解/ 不喜欢重复造轮子,不喜欢贴各种东西.JDK代码什么的,让整篇文章很乱...JDK源码谁都有,没什么好贴的...如果你没看过JDK源码,建议打开Eclipse边看源码边看这篇文章,看过的可以把这篇文章当成是知识点备忘录... JDK容器类中有大量的空指针.数组越界.状态异常等异常处理,这些不是重点,我们关注的应该是它的一些底层的具体实现,这篇

如何阅读Java源码 阅读java的真实体会

刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧,如果你从来没有学过Java,或是任何一门编程语言如C++,一开始去啃<Core Java>,你是很难从中吸收到营养的,特别是<深入Java虚拟机>这类书,别人觉得好,未必适合现在的你. 虽然Tomcat的源码很漂亮,但我绝不建议你一开始就读它.我文中会专门谈到这个,暂时不展开. 强烈

Memcache-Java-Client-Release源码阅读(之七)

一.主要内容 本章节的主要内容是介绍Memcache Client的Native,Old_Compat,New_Compat三个Hash算法的应用及实现. 二.准备工作 1.服务器启动192.168.0.106:11211,192.168.0.106:11212两个服务端实例. 2.示例代码: String[] servers = { "192.168.0.106:11211", "192.168.0.106:11212" }; SockIOPool pool =

源码阅读笔记 - 1 MSVC2015中的std::sort

大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格式化,去掉或者展开用于条件编译或者debug检查的宏,依重要程度重新排序函数,但是不会改变命名方式(虽然MSVC的STL命名实在是我不能接受的那种),对于代码块的解释会在代码块前(上面)用注释标明. template<class _RanIt, class _Diff, class _Pr> in

JDK 源码 阅读 - 2 - 设计模式 - 创建型模式

A.创建型模式 抽象工厂(Abstract Factory) javax.xml.parsers.DocumentBuilderFactory DocumentBuilderFactory通过FactoryFinder实例化具体的Factory. 使用例子: DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilder

CI源码阅读

CodeIgniter源码分析 http://calixwu.com/2014/11/codeigniter-yuanmafenxi.html CI框架源码阅读笔记 http://www.cnblogs.com/ohmygirl/p/4052686.html

《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecut