龙空技术网

「墨积累」EnumMap和EnumSet 的分析

coda九言欢 271

前言:

此刻看官们对“mysqlenumset”可能比较注意,兄弟们都想要了解一些“mysqlenumset”的相关资讯。那么小编在网络上汇集了一些有关“mysqlenumset””的相关资讯,希望大家能喜欢,看官们快快来学习一下吧!

EnumMap

顾名思义,EnumMap是一个的key为Enum的映射

对于需要 枚举->value 的映射时,此数据结构在查找,遍历时有比hashMap更好的性能

数据结构

EnumMap的实现原理比较简单——当key有固定的取值范围,那只需要一个数组来承接value就可以了

下面附上EnumMap的部分源码

    private final Class<K> keyType;    /**     * All of the values comprising K.  (Cached for performance.)     */    private transient K[] keyUniverse;    /**     * Array representation of this map.  The ith element is the value     * to which universe[i] is currently mapped, or null if it isn't     * mapped to anything, or NULL if it's mapped to null.     */    private transient Object[] vals;    /**     * The number of mappings in this map.     */    private transient int size = 0;

与想象中相似,key为枚举的数组,valuse为对象数组,size用来标记遍历数组的界限

为什么可以用一个数组来标识呢?

我们可以知道key的数量,也就是size的最大值,所以可以直接使用数组,也不必考虑扩容

相比于hashMap有 2^n 的容量,enumMap有固定的size,所有key和value都在长度为

enum.values().length 的数组中,数据更加紧凑,节省空间,遍历key和value的效率都更高

EnumMap的get操作

EnumMap最经常使用的get操作,相比于HashMap有哪些优点?

先上源码

    public V get(Object key) {        return (isValidKey(key) ?                unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);    }

可见,数组的下标即为枚举值的 ordinal。

因为EnumMap本质上就是一个数组,get操作和访问数组元素的效率是相当的,这里只是多做了判空的操作

相比于hashMap需要计算hashcode,enumMap无疑更高效

(hashmap在碰撞的情况下需要计算equals,虽较少出现,不过也是一个会影响性能的因素)

为什么计算hashcode的效率会更低?

举个例子,这是String的hashCode()方法,作为比较常用的,作为map的key,是比较有参考性的。

public int hashCode() {        int h = hash;        if (h == 0 && value.length > 0) {            char val[] = value;            for (int i = 0; i < value.length; i++) {                h = 31 * h + val[i];            }            hash = h;        }        return h;    }

由源码可以看出,计算String的hashcode的时候,对字符串进行了遍历,复杂度为O(length),所以也不建议使用长的字符串作为hashMap的key

暂时岔开话题,在计算hashCode的时候,对这些数字常量进行解释

Q:第3行为何有 h==0

A:null 的 ASCII 值为 0, 既是判空,也是判断是否进行过hash

Q:第7行为什么是31

A: 简单来说,31是素数更容易产生唯一性,且 31*h = (h<<5) - h ,位运算具有更快的效率

Q:那为什么不能是33(假设33是素数)

A:33*h = (h<<5) + h 加法可能导致溢出

如果想获取更详细地对hashCode的解释,可以在baeldung上搜索hashCode。在此就不做重复内容的搬运工了

EnumMap特性总结

其实了解了get操作和数据结构,其他的特性也就可以顺势推导出来了,不用过多解释

EnumMap相对于HashMap,更节省内存,也更具效率

EnumSet

在了解了EnumMap的情况下,我们就对EnumSet非常了解了吗?NO

Q:我用enumMap的方式实现一个set,会有什么问题吗?

用是可以用的,但是有个小问题,对于一个STL,应该要对自己有更高要求

相比于基于相同原理的HashSet和HashMap,EnumSet有自己的想法

问题很容易想到了:如果有n个枚举值,我就要用掉n那么大的数组吗?

这显然是不用的,枚举在判断是否存在于某个set的时候,我们可以只把它当作数字

这时,我们就很容易想到了 bitset,用二进制的1和0来标记对应下标的枚举是否存在 ,每一位去标记一个枚举是否存在就好了,这样每个bit就能保存下来8个枚举,非常节省空间。

举个例子,最多有8个枚举,第1,3,4个枚举值存在的set,用00001101 这个二进制数字表示就够了

也就是说,一个long类型的数字,就可以标志64个枚举值是否存在于set中——这太节省空间了

EnumSet的数据结构

不出所料,当length小于64的时候,会构造RegularEnumSet,反之构造JumboEnumSet

    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {        Enum<?>[] universe = getUniverse(elementType);        if (universe == null)            throw new ClassCastException(elementType + " not an enum");        if (universe.length <= 64)            return new RegularEnumSet<>(elementType, universe);        else            return new JumboEnumSet<>(elementType, universe);    }
EnumSet的contains操作

对于RegularEnumSet

同样的,我们可以发现,此时通过一个long来标记64个枚举是否存在于set中,通过移位操作,可以很高效的返回元素是否存在

     private long elements = 0L;    public boolean contains(Object e) {        if (e == null)            return false;        Class<?> eClass = e.getClass();        if (eClass != elementType && eClass.getSuperclass() != elementType)            return false;        return (elements & (1L << ((Enum<?>)e).ordinal())) != 0;    }

对于JumboEnumSet

大差不差,大于64个的情况下,是通过多个long来标记元素是否存在的

private long elements[];    // Redundant - maintained for performance    private int size = 0;    JumboEnumSet(Class<E>elementType, Enum<?>[] universe) {        super(elementType, universe);        elements = new long[(universe.length + 63) >>> 6];    }

第8行右移六位与除以64等效

特性总结

理解了contains操作,就不对其他操作进行分析了,都是为contains服务的数学运算

EnumSet的实现形式与EnumMap不同,EnumSet使用极小的数据空间,有着极高的性能

写在文末

面试场景中,HashMap作为最老牌的八股文,大家都背得滚瓜烂熟了,背完八股文再讲解一下EnumMap,岂不是b格一下就上来了?当然啦,技术的分享并不只用来面试,了解原理可以让我们在合适的时候选择更好数据结构,让程序有更好的性能。

如果觉得文章对你有帮助的话,可以点个赞或者关注哦,感激不尽

第一次在头条写文章,如果有可以改进的意见可以在评论中留言哦

标签: #mysqlenumset