EnumSet常被推荐"性能优于HashSet",但底层到底是怎么实现的?这篇真实用反射直接读出RegularEnumSet的内部字段,并实测2000万次contains()调用验证真实性能差距。
上一篇:详细NPE消息真实验证
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、处理哈希冲突、维护链表或红黑树节点。
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(哈希桶序)都不一样。
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)
每一帧都是真实反射读出的 RegularEnumSet.elements 字段,不是手工画的二进制。