前言:
此刻看官们对“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