bigywater.tech
Java 内部原理 · 127

EnumSet真实验证:反射读出内部真的是一个long类型的位向量

EnumSet常被推荐"性能优于HashSet",但底层到底是怎么实现的?这篇真实用反射直接读出RegularEnumSet的内部字段,并实测2000万次contains()调用验证真实性能差距。

内部真实是一个long
真实反射读取:5个枚举常量的EnumSet,内部elements字段真实是一个long,二进制值恰好是11111
真实快4.7倍
真实实测:2000万次contains()调用,EnumSet真实耗时7ms,同等HashSet真实耗时33ms

上一篇:详细NPE消息真实验证

真实反射验证:EnumSet内部真的是位向量

EnumSet.of()创建的是RegularEnumSet(枚举常量数不超过64个时),真实反射读出它的elements字段看是什么类型。

EnumSetMapInternalsDemo.java enum Day { MON, TUE, WED, THU, FRI, SAT, SUN } EnumSet<Day> workdays = EnumSet.of(MON, TUE, WED, THU, FRI); Field elementsField = RegularEnumSet.class.getDeclaredField("elements");
真实实测
真实EnumSet内容: [MON, TUE, WED, THU, FRI]
真实EnumSet实现类: class java.util.RegularEnumSet
真实反射读取到的内部位向量(二进制): 11111
真实验证: MON(ordinal=0)对应bit0=1, SAT(ordinal=5)对应bit5=0

真实结果证明:5个枚举常量(MON到FRI,ordinal分别是0到4)的EnumSet,内部真实只是一个long类型的elements字段,二进制值恰好是11111——第0到4位(对应MON到FRI)都是1,第5、6位(SAT、SUN)是0。add()、contains()这些操作真实都退化成了对这一个long做位运算(|=、&),不需要像HashSet那样计算hashCode、处理哈希冲突、维护链表或红黑树节点。

真实验证:EnumMap遍历顺序真实按ordinal排序

EnumSetMapInternalsDemo.java EnumMap<Day, String> schedule = new EnumMap<>(Day.class); schedule.put(Day.WED, "会议"); // 先插入WED schedule.put(Day.MON, "晨会"); // 后插入MON
真实实测
真实EnumMap遍历顺序(应按ordinal排序,不是插入顺序): {MON=晨会, WED=会议}

真实结果证明尽管WED先插入、MON后插入,但遍历结果真实是MON排在前面——因为EnumMap内部真实是按枚举常量的ordinal顺序组织存储(用一个和枚举常量数等长的数组,下标就是ordinal),和插入顺序完全无关,这一点和LinkedHashMap(插入序)、HashMap(哈希桶序)都不一样。

真实实测:2000万次contains()调用的性能对比

EnumSetMapInternalsDemo.java for (int i = 0; i < 20_000_000; i++) workdays.contains(Day.WED); for (int i = 0; i < 20_000_000; i++) hashSet.contains(Day.WED);
真实实测
真实实测 20000000 次 contains(): EnumSet=7ms, HashSet=33ms (命中次数校验: true)
真实性能差距来自"完全不需要哈希"这个结构性优势。HashSet的contains()真实需要计算枚举值的hashCode()、定位哈希桶、再遍历桶内链表逐一equals()比较;而EnumSet的contains()真实只是(elements & (1L << e.ordinal())) != 0这一次位运算,时间复杂度和常数因子都远小于HashSet。真实测出的4.7倍差距,加上EnumSet不需要额外的Entry对象、链表节点这些包装开销,内存占用也远小于HashSet——这也是为什么"用枚举做key/元素时优先选EnumSet/EnumMap"是一条真实有性能依据的建议,而不只是代码风格偏好。

分步演示:位向量是怎么一位一位填起来的

每一帧都是真实反射读出的 RegularEnumSet.elements 字段,不是手工画的二进制。

连续 add(MON..FRI):RegularEnumSet.elements 这个 long 里的每一位真实是怎么被置位的
这一步刚被置位的 bit这一步被读取判断的 bit

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,位向量反射结果和性能对比数据重复运行结果一致。
    • 和位运算与HashMap一起看,能更完整理解JDK集合框架里"用位运算替代通用哈希结构"这一类针对性优化的真实收益。
    • 没有涉及:枚举常量超过64个时EnumSet真实切换到JumboEnumSet(用long数组而不是单个long)的行为、EnumMap在ordinal不连续(部分枚举值从未使用)场景下的真实空间浪费——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号