【问题标题】:Enum.values() vs EnumSet.allOf( ). Which one is more preferable?Enum.values() 与 EnumSet.allOf()。哪一个更可取?
【发布时间】:2011-01-28 17:27:33
【问题描述】:

我在后台查看了 EnumSet.allOf,它看起来非常高效,尤其是对于值少于 64 个的枚举。

基本上所有集合共享所有可能的枚举值的单个数组,唯一的其他信息是位掩码,在allOf 的情况下一举设置。

另一方面,Enum.values() 似乎有点黑魔法。此外,它返回的是一个数组,而不是一个集合,因此在许多情况下,它必须用 Arrays.asList() 修饰才能在任何需要集合的地方使用。

那么,EnumSet.allOf 应该比Enum.values 更可取吗?

更具体地说,应该使用哪种形式的 for 迭代器:

for ( final MyEnum val: MyEnum.values( ) );

for ( final MyEnum val: EnumSet.allOf( MyEnum.class ) );

【问题讨论】:

    标签: java enums


    【解决方案1】:

    您应该使用对您来说最简单、最清晰的方法。在大多数情况下,性能不应成为考虑因素。

    恕我直言:这两个选项的性能都不是很好,因为它们都创建了对象。第一种情况下一个,第二种情况下三个。出于性能原因,您可以构造一个包含所有值的常量。

    【讨论】:

    • 2010 年了,创建对象仍然不是免费的。对于大多数编程而言,创建对象并不重要,但如果性能真的很重要,那么您创建的对象数量仍然会产生影响。
    • 我参与过一个项目,其中在关键路径中创建的每个对象每年的成本超过 200 美元。因此,在某些情况下,三个对象可能听起来很昂贵,尤其是如果您不止一次这样做。
    • 不要在循环中实例化对象,在循环外实例化它。
    【解决方案2】:

    如果您只想遍历所有可能的枚举值,values() 方法会更加清晰和高效。这些值由类缓存(参见Class.getEnumConstants()

    如果您需要值的子集,您应该使用EnumSet。以allOf()noneOf() 开头,然后根据需要添加或删除值或仅使用of()

    【讨论】:

    • values() 不能被类缓存,因为它是一个数组,没有什么能阻止用户改变它的值。因此,我怀疑它一定是克隆。另一方面,EnumSet.allOf 确实为数组使用了共享值,因此这里的内存分配肯定更少。所以,values 可能更清晰,但我怀疑它的性能并不高。
    • @Alexander:你是对的,数组是克隆的,但是 clone() 是原生的。一点调试告诉我 getEnumConstants() 使用 values() 而不是相反。
    【解决方案3】:

    并不是说我完成了整个实现,但在我看来,EnumSet.allOf() 基本上使用与 .values() 相同的基础架构。所以我希望 EnumSet.allOf() 需要一些(可能可以忽略不计的)额外步骤(参见http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6276988)。

    我似乎很清楚 foreach 的预期用途是 for(MyEnum val : MyEnum.values()) 为什么会有所不同?你只会迷惑维护程序员。

    我的意思是,如果你需要一个收藏,你应该得到一个。如果你想使用 foreach,数组就足够了。如果按下,我什至更喜欢数组!如果你得到的(数组)足够好,为什么要用任何东西包装任何东西?简单的事情通常更快。

    无论如何,Peter Lawrey 是对的。不要担心这个的性能..它足够快,并且可能还有数百万个其他瓶颈使得微小的理论性能差异完全无关紧要(但不要看到他的“对象创建”点。对我来说,第一个示例似乎是 100% OK)。

    【讨论】:

    • @Zwei:查看我对 Arne 帖子的评论
    • @Alexander:好的,所以他们修复了 JDK6 中的错误(见链接)?好吧,我明白你的意思,但我仍然坚持你的问题“更具体地说,应该使用哪种形式的迭代器”作为“使用第一个例子”的答案。我的意思是,我不知道。如果您正在嵌入式实时应用程序中进行一些开发。什么的,也许这是有道理的。但是在正常的一般情况下呢?没有。
    【解决方案4】:

    还有Class.getEnumConstants()

    无论如何,他们都调用枚举类型的values() 方法,通过反射

    【讨论】:

    • 这与我提出的问题有什么关系?
    • 这与问题有关,因为所有其他人都在后台使用 values()。
    【解决方案5】:

    因为我没有收到关于哪个更有效的问题的答案,所以我决定自己做一些测试。

    我已经在values()Arrays.asList( values() )EnumSet.allOf( ) 上测试了迭代。 我已经针对不同的枚举大小重复了这些测试 10,000,000 次。以下是测试结果:

    oneValueEnum_testValues         1.328
    oneValueEnum_testList           1.687
    oneValueEnum_testEnumSet        0.578
    
    TwoValuesEnum_testValues        1.360
    TwoValuesEnum_testList          1.906
    TwoValuesEnum_testEnumSet       0.797
    
    ThreeValuesEnum_testValues      1.343
    ThreeValuesEnum_testList        2.141
    ThreeValuesEnum_testEnumSet     1.000
    
    FourValuesEnum_testValues       1.375
    FourValuesEnum_testList         2.359
    FourValuesEnum_testEnumSet      1.219
    
    TenValuesEnum_testValues        1.453
    TenValuesEnum_testList          3.531
    TenValuesEnum_testEnumSet       2.485
    
    TwentyValuesEnum_testValues     1.656
    TwentyValuesEnum_testList       5.578
    TwentyValuesEnum_testEnumSet    4.750
    
    FortyValuesEnum_testValues      2.016
    FortyValuesEnum_testList        9.703
    FortyValuesEnum_testEnumSet     9.266
    

    这些是从命令行运行的测试结果。当我从 Eclipse 运行这些测试时,我得到了对 testValues 的压倒性支持。基本上它比EnumSet 小,即使对于小枚举也是如此。我相信性能提升来自于for ( val : array )循环中数组迭代器的优化。

    另一方面,只要你需要一个 java.util.Collection 来传递,Arrays.asList( ) 就会被 EnumSet.allOf 取代,特别是对于小型枚举,我相信在任何给定的代码库中都将占多数。

    所以,我会说你应该使用

    for ( final MyEnum val: MyEnum.values( ) )
    

    但是

    Iterables.filter(
        EnumSet.allOf( MyEnum.class ),
        new Predicate< MyEnum >( ) {...}
    )
    

    并且仅在绝对需要java.util.List 的情况下使用Arrays.asList( MyEnum.values( ) )

    【讨论】:

      【解决方案6】:

      EnumSet 不是为了迭代它的值而构建的。相反,它的实现是为了有效地(或相当有效地)表示位图或位掩码。 javadoc on EnumSet 还声明:

      枚举集在内部表示为位向量。这种表示非常紧凑和高效。这个类的空间和时间性能应该足够好,以允许它用作传统的基于 int 的“位标志”的高质量、类型安全的替代品。如果参数也是枚举集,即使是批量操作(例如 containsAll 和 retainAll)也应该运行得非常快。

      因为只有一个位可以表示某个 Enum 值,所以也实现为Set,而不是List

      现在,您可能也可以使用 C 样式的位掩码 (x^2) 更快地完成相同的操作,但是它提供了更直观的编码风格和使用枚举的类型安全使用,并且它扩展了很容易超出intlong 可以包含的大小。

      因此,您可以测试所有位设置如下:

      public class App {
        enum T {A,B}
        public static void main(String [] args) {
          EnumSet<T> t = EnumSet.of(T.A);
          t.containsAll(EnumSet.allOf(T.class));
        }
      }
      

      【讨论】:

      • 你完全倒退了。 EnumSet 首先是Collection,实际上是Set。由于枚举的属性,事实证明这种集合的最有效表示是位掩码。另外,请注意您的示例中的containsAll 不是EnumSet 独有的,它是Set 中的一个方法。但是除此之外,您没有回答最初发布的问题,当您需要访问枚举中的所有值时,哪种形式更有效。
      • @AlexanderPogrebnyak 证实了我完全落后的情况,因为我不清楚。我从来没有声称EnumSet 不是Set。因此,containsAllretainAll 之类的方法自然不是 EnumSet 独有的,尽管它们具有完全独特的实现。就答案而言,您似乎提供了一个很好的指标。我只是想补充一下,因为我认为我不应该仅基于对完整枚举值集的迭代做出选择。
      • 有时迭代完整的枚举值集是您唯一的选择。例如,当您必须验证并将外部传递的值分配给枚举时,并且该值没有明确映射到枚举名称,因此您不能使用Enum.valueOf
      • @AlexanderPogrebnyak 迭代一次以构建Map,然后使用映射将您的“外部值”转换为enum 常量。您可以实现一个与内置valueOf 方法类似的便捷方法。这种方法使您最初的问题也无关紧要,因为构建地图应该只发生一次,作为您的 enum 的静态初始化的一部分。
      猜你喜欢
      • 2017-07-17
      • 1970-01-01
      • 2017-11-26
      • 2012-07-15
      • 2020-06-16
      • 1970-01-01
      • 1970-01-01
      • 2020-03-14
      相关资源
      最近更新 更多