2010-03-17 83 views
55

我仔细看了下EnumSet.allOf,看起来效率很高,特别是对于少于64个值的枚举。Enum.values()vs EnumSet.allOf()。哪一个更可取?

基本上所有的集合都共享所有可能的枚举值的单个数组,并且唯一的另一条信息是一个位掩码,在allOf的情况下被设置为一次。

另一方面Enum.values()似乎有点黑魔法。此外,它返回一个数组,而不是一个集合,所以在许多情况下,它必须用Arrays.asList()来装饰,以便在任何需要收集的地方都可用。因此,EnumSet.allOf更适合Enum.values

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

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

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

回答

83

因为我没有收到回答我的问题在哪一个更有效,我决定做一些我自己的测试。

我测试了迭代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>() {...} 
) 

而且只使用Arrays.asList(MyEnum.values())那里是绝对必要的java.util.List

+2

@ alexander-pogrebnyak,将其标记为答案 – GetUsername 2011-03-31 14:03:05

+1

@GetUsername不想在没有人投票的情况下做:D – 2011-03-31 17:44:54

+1

很好的答案+1 – PiersyP 2013-06-25 20:57:33

12

你应该使用哪一个是最简单和最清晰的,以你的方式。在大多数情况下,性能不应该被考虑。

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

+10

创建三个对象作为性能的考虑?队友,这不是1995年了... – 2010-03-17 20:36:14

+8

2010年,并创建一个对象仍然是不自由的。对于大多数编程来说,创建对象并不重要,但如果性能确实很重要,则创建的对象数量可能会有所不同。 – 2010-03-20 18:24:27

+2

我曾参与过一个项目,其中关键路径中创建的每个对象每年的成本超过200美元。因此,在某些情况下,三个物体听起来可能会很昂贵,特别是如果您不止一次这样做。 – 2010-03-20 18:28:48

4

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

如果您需要值的子集,则应使用EnumSet。从allOf()noneOf()开始,并根据需要添加或删除值或仅使用of()

+1

这个'values()'不能被类缓存,因为它是一个数组,没有什么会阻止用户改变它的值。因此,我怀疑它一定是一个克隆。另一方面,'EnumSet.allOf'确实使用数组的共享值,因此这里的内存分配肯定较少。所以,“价值”可能会更清楚,但我怀疑这不是更高性能。 – 2010-03-17 21:07:30

+1

@Alexander:你是对的,数组被克隆,但clone()是本地的。一些小小的调试显示,getEnumConstants()使用values()并非相反。 – 2010-03-18 08:40:27

2

不是我经历了整个实现,但在我看来,EnumSet.allOf()基本上使用与.values()相同的基础结构。所以我期望EnumSet.allOf()需要一些(可能可以忽略的)附加步骤(请参阅http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6276988)。

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

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

无论如何,彼得劳里是正确的。不要担心这种表现。它速度很快,而且有很多其他瓶颈可能会使这种微小的理论性能差异变得完全不相关(尽管没有看到他的“对象创造”点,但对我而言,第一个示例似乎是100%OK)。

+0

@Zwei:看到我对Arne的文章的评论 – 2010-03-17 21:10:53

+0

@Alexander:好的,他们修复了JDK6中的bug(请参阅链接)?那么,我明白你的观点,但我仍然保留对你的问题的答案:“更具体地说,应该使用迭代器的哪种形式”作为“使用第一个例子”。我的意思是,我不知道。如果您正在开发嵌入式实时应用程序。或者什么的,也许这是合理的。但在一个正常的,一般的情况下? No. – 2010-03-17 21:42:39

7

还有Class.getEnumConstants()

引擎盖下他们所有的呼叫enum类型的values()方法反正通过反射

+0

这与我所问的问题有何关系? – 2010-03-18 16:26:24

+2

这涉及到这个问题,因为所有其他人都使用引擎盖下的values()。 – 2013-07-24 20:19:00

0

EnumSet不是有意向建遍历它的价值观。相反,它的实现与它的想法是代表一个BitMap或BitMask高效(或相当有效)。 javadoc on EnumSet还指出:

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

由于只有一个单个位可以表示一定的枚举值,则还实现为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)); 
    } 
} 
+0

你完全倒退了。 'EnumSet'首先是一个'Collection',实际上是一个'Set'。由于枚举的属性,事实证明这样一个集合的最有效表示是一个位掩码。另外,请注意,在您的示例中'containsAll'对于'EnumSet'不是唯一的,它是'Set'的一个方法。但是,除此之外,您没有回答最初发布的问题,当您需要访问枚举中的所有值时,哪种形式更有效。 – 2016-02-02 11:36:53

+0

@AlexanderPogrebnyak证实了我确切的落后,因为我不清楚。我从来没有提出任何有关'EnumSet'是'Set'以外的任何声明。因此,像'containsAll'和'retainAll'这样的方法对于EnumSet来说并不是唯一的,尽管它们具有完全独特的实现。至于答案,你似乎已经提供了一个很好的指标。我只是想补充一点,因为我不相信我应该仅仅基于对整个枚举值的迭代来做出选择。 – YoYo 2016-02-02 12:00:06

+0

有时遍历整个枚举值集合是您唯一的选择。例如,当您必须验证并将外部传递的值分配给枚举时,并且该值没有清楚地映射到枚举名称,因此您不能使用'Enum.valueOf'。 – 2016-02-02 12:29:10