使用 enum.values() 和 String 数组时会有性能损耗吗?

28

我在我的Java应用程序(JRE 1.5)中使用枚举来替代String常量。

如果我将枚举视为一个静态名称数组并在不断调用的方法中使用它(例如渲染UI时),是否会影响性能?

我的代码看起来有点像这样:

public String getValue(int col) {
  return ColumnValues.values()[col].toString();
}

澄清:

  • 我关心反复枚举values()(例如在paint()方法内部)相关的隐藏成本。
  • 我现在可以看到,所有我的场景都包括一些int => enum转换 - 这不是Java的方式。

提取values()数组的实际代价是多少?这是一个问题吗?

Android开发者

请阅读Simon Langhoff在下面的答案,Geeks On Hugs已经在被接受的答案的评论中指出。 Enum.values() 必须进行防御性复制

7个回答

27
对于枚举类型,为了保持不可变性,每次调用Values()方法时都会克隆支持数组。这意味着它将会对性能产生影响。具体取决于您的特定情况。
我一直在监控我的Android应用程序,并发现在我的特定情况下,这个简单的调用使用了13.4%的CPU时间!
为了避免克隆值数组,我决定将这些值作为私有字段缓存,然后在需要时循环遍历这些值:
private final static Protocol[] values = Protocol.values();

在进行这个小优化后,我的方法调用只占用了可忽略的 0.0% CPU时间
在我的使用情况下,这是一个受欢迎的优化,然而,需要注意的是,使用这种方法会牺牲你的枚举类型的可变性。一旦你给他们引用,谁知道人们可能会把什么放进你的值数组中!?

枚举如何不是不可变的?你无法在运行时添加它们。 - skiwi
5
在这种情况下,当我询问此枚举类型可能的值时,会得到一个包含这些值的数组的引用。由于我有了这个引用,我可以向其中添加元素,下次有人询问此枚举类型的值时,他们将获得对我刚刚操作的同一个数组的引用。因此,任何已经请求获取此枚举类型值的人都将看到之前对支持数组所做的更改。 - Simon Langhoff
好的,我现在明白你的意思了。 - skiwi
这是绝对正确的!我一直在追踪一个恶性内存泄漏(在48kHz的音频合成器中,20秒内达到了3Gb)。我正在使用values()迭代枚举,将其复制到静态引用中可以消除此问题。 - Tim V
防止向这样的数组添加元素,不会让您重新获得枚举的不可变性吗? - Anddo

20
< p > Enum.values() 会给你一个数组的引用,遍历枚举数组的成本与遍历字符串数组的成本相同。同时,将枚举值与其他枚举值进行比较实际上比将字符串与字符串进行比较更快。

如果你担心调用 values() 方法的成本,而已经有了对数组的引用,那就不用担心了。在Java中,方法调用现在是非常快的,任何时候它实际上对性能有影响时,编译器都会将方法调用内联。

所以,认真地,不用担心。 集中精力于代码的可读性,并使用Enum,这样编译器就会捕获到您曾试图使用代码未预期处理的常量值。


如果你好奇为什么枚举比字符串比较快,这里是详细信息:
这取决于字符串是否已经被interned。对于Enum对象,在系统中每个枚举值只有一个实例,因此每次调用Enum.equals()都可以非常快地完成,就像使用==运算符而不是equals()方法一样。实际上,对于Enum对象,使用==而不是equals()是安全的,而对于字符串来说,这是不安全的。
对于字符串,如果字符串已经被interned,则比较与Enum一样快。但是,如果字符串没有被interned,则String.equals()方法实际上需要遍历两个字符串中的字符列表,直到其中一个字符串结束或发现两个字符串之间不同的字符为止。
但是,即使在必须快速执行的Swing渲染代码中,这通常也不重要。 :-)

@Ben Lings指出,Enum.values()必须进行防御性复制,因为数组是可变的,而且你可能会替换由Enum.values()返回的数组中的一个值。这意味着你必须考虑防御性复制的成本。然而,复制单个连续的数组通常是一个快速操作,假设它是在"底层"使用某种内存复制调用来实现的,而不是简单地迭代数组中的元素。所以,我认为这并不改变最终的答案。


谢谢!这正是我想知道的。最重要的是,每个人都说不用担心 - 这也是我要做的... - Asaf
10
Enum.values()无法提供静态数组的引用。如果您更改其中一个值,会发生什么?每次调用该方法时,都必须复制它。 - Ben Lings
1
好的观点 - 在获取数组后,您可以对其进行突变,因此需要进行防御性复制。他们更应该为您提供一个不可变集合进行迭代,但是他们没有做出在这里有意义的事情,是吗?;-) 我会相应地更新我的答案。 - Joe Carnahan
1
如果您发现由于某种原因导致性能受到影响,您可以始终保留枚举值数组的本地缓存。 - deterb
4
所以,严肃地说,不用担心它。在移动设备上需要尽量减少开销时怎么办?我想知道是否可以只使用一些字节常量而不是创建枚举类型?(特定于Android) - Geeks On Hugs

2
作为一个经验法则:在考虑优化之前,你有没有想过这段代码会减慢你的应用程序?
现在来看看事实吧。
枚举类型在很大程度上是编译过程中散布的语法糖。因此,对于枚举类定义的values方法返回一个静态集合(也就是在类初始化时加载),其性能可以被认为与数组相当。

是的,我知道:“过早优化是万恶之源”。但是这段代码是在一个经常被渲染的JTable渲染器中完成的。然而,我正在寻找有人告诉我编译器会处理一切,并且这里没有任何陷阱(比如使用反射或其他什么)。 - Asaf
正如Aaron所说,将枚举条目存储在表格模型中要好得多,而不是存储其索引。这样,就不需要在枚举列表中进行查找了。这正是TableModel存在的原因,你知道的... - Riduidel

2

如果您关心性能,那么请进行测量。

从代码来看,我不会期望有任何意外,但是90%的性能猜测都是错误的。如果您想要更安全,请考虑将枚举移至调用代码中(即public String getValue(ColumnValues value) {return value.toString();})。


如果您在源代码中使用枚举,那么就不会有性能损失,因为您根本不需要进行转换(或者至少可以节省一个方法调用)。此外,当您在错误的位置开始使用枚举时,您将会得到错误提示。 - Aaron Digulla

2

使用这个:

private enum ModelObject { NODE, SCENE, INSTANCE, URL_TO_FILE, URL_TO_MODEL,
    ANIMATION_INTERPOLATION, ANIMATION_EVENT, ANIMATION_CLIP, SAMPLER, IMAGE_EMPTY,
    BATCH, COMMAND, SHADER, PARAM, SKIN }
private static final ModelObject int2ModelObject[] = ModelObject.values();

1
如果您只是为了查找特定的枚举值而遍历枚举值,那么您可以将枚举值静态映射到整数。这将使性能影响在类加载时发挥作用,并且使得基于映射参数获取特定枚举值变得简单/低影响。
public enum ExampleEnum {
    value1(1),
    value2(2),
    valueUndefined(Integer.MAX_VALUE);

    private final int enumValue;
    private static Map enumMap;
    ExampleEnum(int value){
       enumValue = value;
    }
    static {
       enumMap = new HashMap<Integer, ExampleEnum>();
       for (ExampleEnum exampleEnum: ExampleEnum.values()) {
           enumMap.put(exampleEnum.value, exampleEnum);
        }
    }
    public static ExampleEnum getExampleEnum(int value) {
        return enumMap.contains(value) ? enumMap.get(value) : valueUndefined;
    }
}

谢谢@jdoe7777777。我经常使用类似的模式。虽然它不是我的问题的答案,但仍然很有帮助。 - Asaf

-1

我认为是的。而且使用常量更方便。


1
也许在开发过程中更方便,但这会成为一个维护的噩梦。 - Aaron Digulla
这要看情况。如果代码是由半个开发人员和半个塞满东西的人编写的,那么可能是真的。 - Artsiom Anisimau
5
我同意Aaron的观点。在我看来,枚举比常量更好用——它们提供了类型安全性,这样如果你试图在代码中使用一个值,而你应该使用另一个值,编译时会出现错误,而字符串常量无法防止此类错误。此外,枚举还有其他好处,例如能够表达行为。 - Jon Skeet
https://dev59.com/0nI-5IYBdhLWcg3wbXtN + https://dev59.com/HXVD5IYBdhLWcg3wKYT-。我没有发明任何新东西... - Asaf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接