2010-03-17 14 views
55

Miré debajo del capó para EnumSet.allOf y parece muy eficiente, especialmente para enums con menos de 64 valores.Enum.values ​​() vs EnumSet.allOf(). ¿Cuál es más preferible?

Básicamente todos los conjuntos comparten la única matriz de todos los posibles valores enum y la única otra información es una máscara de bits que en el caso de allOf se establece de una sola vez.

Por otro lado Enum.values ​​() parece ser un poco de magia negra. Además, devuelve una matriz, no una colección, por lo que en muchos casos debe decorarse con Arrays.asList() para que se pueda utilizar en cualquier lugar que espere la recopilación.

¿Entonces, debería ser EnumSet.allOf ser más preferible a Enum.values?

Más específicamente, qué forma de for iterador se debe utilizar:

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

o

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

Respuesta

83

Como no recibí la respuesta a mi pregunta sobre cuál es más eficiente, he decidido hacer algunas pruebas por mi cuenta.

He probado la iteración sobre values(), Arrays.asList(values()) y EnumSet.allOf(). He repetido estas pruebas 10,000,000 veces para diferentes tamaños de enumeración. Aquí están los resultados de la prueba:

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 

Estos son los resultados de las pruebas ejecutadas desde la línea de comandos. Cuando realicé estas pruebas desde Eclipse, obtuve un apoyo abrumador para testValues. Básicamente, era más pequeño que EnumSet, incluso para enumeraciones pequeñas. Creo que la ganancia de rendimiento proviene de la optimización del iterador de matriz en el for (val : array) loop.

Por otro lado, tan pronto como necesite pasar java.util.Collection, Arrays.asList() se pierde en EnumSet.allOf, especialmente para las enumeraciones pequeñas, que creo que serán mayoría en cualquier código base.

Por lo tanto, yo diría que se debe utilizar

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

pero

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

Y sólo utilizar Arrays.asList(MyEnum.values()) donde se requiere absolutamente java.util.List.

+2

@ alexander-pogrebnyak, márquelo como respuesta – GetUsername

+1

@GetUsername No quería hacerlo sin el voto de alguien: D –

+1

Gran respuesta +1 – PiersyP

12

debería utilizar el enfoque que es más simple y clara para usted. El rendimiento no debe ser una consideración en la mayoría de las situaciones.

En mi humilde opinión: ninguna de las dos opciones funciona muy bien, ya que ambos crean objetos. Uno en el primer caso y tres en el segundo. Podría construir una constante que contenga todos los valores por razones de rendimiento.

+10

Creación de tres objetos como una consideración de rendimiento? Mate, ya no es 1995 ... –

+8

Es 2010, y crear un objeto aún no es gratis. Para la mayoría de la programación, la creación de objetos no importa, pero si el rendimiento realmente importa, la cantidad de objetos que crea puede hacer una diferencia. –

+2

He trabajado en un proyecto donde cada objeto creado en la ruta crítica cuesta más de $ 200 por año. Por lo tanto, tres objetos pueden parecer caros en algunos contextos, especialmente si lo hace más de una vez. –

4

El método values() es más claro y eficaz si solo desea iterar sobre todos los valores de enum posibles. Los valores son almacenados en caché por la clase (consulte Class.getEnumConstants())

Si necesita un subconjunto de valores, debe usar un EnumSet. Comience con allOf() o noneOf() y agregue o elimine valores o use solo of() según lo necesite.

+1

Los 'values ​​()' no pueden ser almacenados en caché por la clase, porque es una matriz y nada impedirá que el usuario cambie sus valores. Por lo tanto, sospecho que debe ser un clon. 'EnumSet.allOf', por otro lado, usa un valor compartido para la matriz, por lo tanto, hay definitivamente menos asignación de memoria aquí. Entonces, 'values' puede ser más claro, pero sospecho que no es más eficiente. –

+1

@Alexander: tienes razón, la matriz está clonada, pero clone() es nativa. Una pequeña depuración me muestra que getEnumConstants() usa values ​​(), no al revés. –

2

No es que haya pasado por toda la implementación, pero me parece que EnumSet.allOf() básicamente está usando la misma infraestructura que .values ​​(). Por lo tanto, esperaría que EnumSet.allOf() requiera algunos pasos adicionales (probablemente insignificantes) (consulte http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6276988).

Me parece claro que el uso previsto de foreach es for(MyEnum val : MyEnum.values()) ¿por qué hacerlo de manera diferente? Solo confundirá al programador de mantenimiento.

Quiero decir, si necesita una colección, debería obtener una. Si desea utilizar un foreach, las matrices son lo suficientemente buenas. ¡Prefiero las matrices si me presionan! ¿Por qué envolver algo con algo, si lo que tienes (matriz) es lo suficientemente bueno? Las cosas simples son normalmente más rápidas.

De todos modos, Peter Lawrey tiene razón. No se preocupe por el rendimiento de esto ... Es lo suficientemente rápido, y es probable que haya millones de otros cuellos de botella que hacen que esa pequeña diferencia de rendimiento teórico sea totalmente irrelevante (sin embargo, no veo su punto de "creación de objetos"). el ejemplo parece estar 100% bien).

+0

@Zwei: ver mi comentario en la publicación de Arne –

+0

@Alexander: OK, ¿entonces arreglaron el error (ver enlace) en JDK6? Bueno, veo tu punto entonces, pero aún mantengo la respuesta a tu pregunta "Más específicamente, qué forma de iterator se debe usar" como "usa el primer ejemplo". Quiero decir, no sé. Si está realizando algún desarrollo en la aplicación incrustada en tiempo real. o algo, tal vez es justificable. Pero en un contexto normal y general? No. –

7

También Class.getEnumConstants()

es bajo el capó que todas las llamadas values() métodos de tipos de enumeración de todos modos, través de la reflexión.

+0

¿Cómo se relaciona esto con la pregunta que hice? –

+2

Esto se relaciona con la pregunta, porque todos los demás usan valores() debajo del capó. –

0

EnumSet no está diseñado con la intención de iterar sobre sus valores. Más bien se implementa con la idea de que represente un BitMap o BitMask de manera eficiente (o razonablemente eficiente). El javadoc on EnumSet también indica:

Los conjuntos de enum se representan internamente como vectores de bits. Esta representación es extremadamente compacta y eficiente. El rendimiento de espacio y tiempo de esta clase debería ser lo suficientemente bueno como para permitir su uso como una alternativa de alta calidad y segura para tipos de "indicadores de bits" tradicionales basados ​​en int. Incluso las operaciones masivas (como containsAll y retainAll) deberían ejecutarse muy rápidamente si su argumento también es un conjunto de enumeración.

Debido a que sólo un bit individual puede representar un cierto valor de enumeración, sino que también se implementa como un Set y no como un List.

Ahora, probablemente también sea cierto que puede lograr lo mismo, y más rápido, usando máscaras de bits estilo C (x^2), sin embargo ofrece un estilo de codificación más intuitivo y escribe un uso seguro usando enumeraciones. se expande fácilmente más allá del tamaño de lo que puede contener un int o long.

Como tal puede probar que todos los bits se establecen de la siguiente manera:

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

Lo tienes absolutamente al revés. 'EnumSet' ante todo es una' Colección', en realidad un 'Conjunto'. Debido a las propiedades de las enumeraciones, resulta que la representación más eficiente de dicho conjunto es una máscara de bits. Además, observe que 'containsAll' en su ejemplo no es exclusivo de' EnumSet', es un método de 'Set'. Pero aparte de eso, no respondió la pregunta publicada originalmente, qué forma es más eficiente cuando necesita acceder a todos los valores en la enumeración. –

+0

@AlexanderPogrebnyak corroboran lo que tengo exactamente al revés, ya que no está claro para mí. Nunca hice ninguna afirmación acerca de que 'EnumSet' fuera otra cosa que un 'conjunto '. En consecuencia, es natural que los métodos como 'containsAll' y' retainAll' no sean exclusivos de 'EnumSet', aunque tienen una implementación completamente única. En cuanto a las respuestas, parecías haber proporcionado una excelente con métricas. Solo tenía la intención de complementar porque no creo que la elección deba hacerse basada solo en una iteración sobre el conjunto completo de valores enum. – YoYo

+0

En ocasiones, iterar sobre el conjunto completo de valores enum es la única opción que tiene. Por ejemplo, cuando tiene que validar y asignar un valor pasado externamente a enum, y ese valor no se asigna claramente al nombre enum, por lo que no puede usar 'Enum.valueOf'. –

Cuestiones relacionadas