【问题标题】:is there a performance hit when using enum.values() vs. String arrays?使用 enum.values() 与字符串数组时是否会影响性能?
【发布时间】:2010-03-15 09:42:40
【问题描述】:

我正在使用枚举来替换我的 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 早先在接受的答案的 cmets 中指出了这一点。 Enum.values()一定要做防守副本

【问题讨论】:

    标签: java performance enums


    【解决方案1】:

    对于枚举,为了保持不变性,它们会在您每次调用 Values() 方法时克隆支持数组。这意味着它将对性能产生影响。多少取决于您的具体情况。

    我一直在监控自己的 Android 应用,发现这个简单的调用在我的具体情况下使用了 13.4% CPU time!

    为了避免克隆值数组,我决定将值简单地缓存为私有字段,然后在需要时循环遍历这些值:

    private final static Protocol[] values = Protocol.values();
    

    经过这个小优化后,我的方法调用只占用了微不足道的0.0% CPU time

    在我的用例中,这是一个受欢迎的优化,但是,重要的是要注意,使用这种方法是对枚举可变性的权衡。一旦你给他们一个引用,谁知道人们会在你的 values 数组中放入什么!?

    【讨论】:

    • 枚举如何不是不可变的?您不能在运行时添加它们。
    • @skiwi 在这种情况下,当我询问此枚举的可能值时,我会得到对包含所述值的数组的引用。因为我有这个引用,所以我可以向它添加元素,下次有人询问这个枚举的值时,他们将获得对我刚刚操作的同一个数组的引用。因此,任何要求此枚举值的人都会看到之前对支持数组所做的任何更改。
    • 对,我明白你的意思了
    • 这绝对是真的!我一直在追踪一个恶性内存泄漏(20 秒内 3Gb(它是一个 48kHz 的音频合成器)。我正在使用 values() 对枚举进行迭代,如上所述复制到静态引用消除了这个问题。
    • 不会阻止添加到这样的数组会让你获得枚举的不变性吗?
    【解决方案2】:

    Enum.values() 为您提供对数组的引用,并且迭代枚举数组的成本与迭代字符串数组的成本相同。同时,将枚举值与其他枚举值进行比较实际上可以更快将字符串与字符串进行比较。

    同时,如果您担心调用 values() 方法而不是已经引用数组的成本,请不要担心。 Java 中的方法调用(现在)非常快,并且任何时候它对性能确实很重要,无论如何编译器都会内联方法调用。

    所以,说真的,不用担心。 专注于代码的可读性,并使用 Enum 以便编译器在你尝试使用你的常量值时捕获它代码无法处理。


    如果您对为什么枚举比较可能比字符串比较更快感到好奇,以下是详细信息:

    这取决于字符串是否为interned。对于Enum 对象,系统中的每个枚举值始终只有一个实例,因此对Enum.equals() 的每次调用都可以非常快速地完成,就像您使用== 运算符而不是@987654328 一样@ 方法。事实上,对于Enum 对象,使用== 代替equals() 是安全的,而对于字符串则安全。

    对于字符串,如果字符串已被保留,则比较与Enum 一样快。但是,如果字符串没有被保留,那么String.equals() 方法实际上需要遍历两个字符串中的字符列表,直到其中一个字符串结束或者它发现两个字符串之间的字符不同。

    但同样,这可能无关紧要,即使在必须快速执行的 Swing 渲染代码中也是如此。 :-)


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

    【讨论】:

    • 谢谢!正是我想知道的。最重要的是,每个人都说不要担心——这就是我要做的……
    • Enum.values() 无法为您提供对静态数组的引用。如果你改变其中的一个值会发生什么?每次调用该方法时我都必须复制它。
    • 好点 - 您可以在获得数组后对其进行变异,因此需要一个防御性副本。他们给你一个 immutable 集合来迭代会更有意义,但他们没有做在这里有意义的事情,不是吗? ;-) 我会相应地更新我的答案。
    • 如果您确实发现(由于某种原因)性能受到影响,您可以始终保留枚举值数组的本地缓存。
    • “所以,说真的,别担心。”当需要尽可能减少开销时,在移动设备上怎么办?我想知道只使用几个字节常量而不是制作枚举类型? (专门针对 Android)
    【解决方案3】:

    根据经验:在考虑优化之前,您是否有任何线索表明此代码可能会降低您的应用程序的速度?

    现在,事实。

    在很大程度上,枚举是散布在编译过程中的语法糖。因此,为枚举类定义的 values 方法返回一个静态集合(即在类初始化时加载),其性能大致相当于一个数组。

    【讨论】:

    • 是的,我知道:“过早的优化是万恶之源”。但是这段代码是在一个 Jtable 渲染器中完成的,它会被渲染很多。但是,我正在寻找一个会告诉我编译器会处理所有事情的人,而且这里没有任何问题(比如使用反射或其他东西)。
    • 就像 Aaron 所说的,将枚举条目存储在表模型中要比其索引好得多。这样,就无需在枚举列表中进行查找。这正是TableModel存在的原因,你懂的……
    【解决方案4】:

    如果您关心性能,请测量。

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

    【讨论】:

    • 如果您在源代码中使用枚举,则不会有性能损失,因为您根本不需要进行转换(或者您至少会保存一个方法调用)。另外,当你在错误的地方开始使用枚举时,你会得到错误。
    【解决方案5】:

    使用这个:

    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();
    

    【讨论】:

      【解决方案6】:

      如果您遍历枚举值只是为了查找特定值,您可以将枚举值静态映射到整数。这推动了对类负载的性能影响,并使基于映射参数获取特定枚举值变得容易/影响很小。

      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。我经常使用类似的模式。这不是我问题的答案,但仍然很有帮助
      【解决方案7】:

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

      【讨论】:

      • 在开发过程中可能更方便,但这是维护的噩梦。
      • 视情况而定。如果半开发者半丰满的代码可能是真的。
      • 我和亚伦在一起。枚举比常量 IMO 更好 - 它们提供类型安全,因此如果您尝试使用代码的一个区域中的值,而您打算使用另一个区域的值,则会出现编译时错误 - 字符串常量不会阻止这一点某种错误。枚举还有其他好处,比如它们能够表达行为。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-03-09
      • 1970-01-01
      • 2010-11-18
      • 2013-12-28
      • 1970-01-01
      • 1970-01-01
      • 2013-06-14
      相关资源
      最近更新 更多