¿Cuál es la forma más eficiente de atravesar una colección?
List<Integer> a = new ArrayList<Integer>();
for (Integer integer : a) {
integer.toString();
}
o
List<Integer> a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();) {
Integer integer = (Integer) iterator.next();
integer.toString();
}
Tenga en cuenta que este no es un duplicado exacto de esto , esto , esto o esto , aunque una de las respuestas a la última pregunta se acerca. La razón por la que esto no es un engaño, es que la mayoría de estos son bucles de comparación donde se llama get(i)
dentro del bucle, en lugar de usar el iterador.
Como se sugiere en Meta , publicaré mi respuesta a esta pregunta.
java
collections
foreach
Paul Wagland
fuente
fuente
Respuestas:
Si solo está deambulando por la colección para leer todos los valores, entonces no hay diferencia entre usar un iterador o la nueva sintaxis de bucle, ya que la nueva sintaxis solo usa el iterador bajo el agua.
Sin embargo, si quiere decir por bucle el viejo bucle "c-style":
Entonces, el nuevo for loop, o iterador, puede ser mucho más eficiente, dependiendo de la estructura de datos subyacente. La razón de esto es que para algunas estructuras de datos,
get(i)
es una operación O (n), lo que hace que el bucle sea una operación O (n 2 ). Una lista vinculada tradicional es un ejemplo de dicha estructura de datos. Todos los iteradores tienen como requisito fundamental quenext()
debería ser una operación O (1), haciendo que el bucle O (n).Para verificar que el nuevo iterador utiliza el iterador bajo el agua para la sintaxis de bucle, compare los códigos de byte generados a partir de los siguientes dos fragmentos de código Java. Primero el bucle for:
Y segundo, el iterador:
Como puede ver, el código de byte generado es efectivamente idéntico, por lo que no hay penalización de rendimiento al usar cualquiera de los formularios. Por lo tanto, debe elegir la forma de bucle que sea más estéticamente atractiva para usted, para la mayoría de las personas que será el bucle para cada, ya que tiene menos código repetitivo.
fuente
for(int i; i < list.size(); i++) {
bucle de estilo también debe evaluarselist.size()
al final de cada iteración; si se usa, a veces es más eficiente almacenar en caché el resultado delist.size()
primero.La diferencia no está en el rendimiento, sino en la capacidad. Al usar una referencia directamente, tiene más poder sobre el uso explícito de un tipo de iterador (por ejemplo, List.iterator () vs. List.listIterator (), aunque en la mayoría de los casos devuelven la misma implementación). También tiene la capacidad de hacer referencia al iterador en su bucle. Esto le permite hacer cosas como eliminar elementos de su colección sin obtener una ConcurrentModificationException.
p.ej
Esto esta bien:
Esto no es así, ya que arrojará una excepción de modificación concurrente:
fuente
Para ampliar la propia respuesta de Paul, ha demostrado que el bytecode es el mismo en ese compilador en particular (¿presumiblemente el javac de Sun?) Pero no se garantiza que diferentes compiladores generen el mismo bytecode, ¿verdad? Para ver cuál es la diferencia real entre los dos, vayamos directamente a la fuente y verifiquemos la Especificación del lenguaje Java, específicamente 14.14.2, "La declaración mejorada para" :
En otras palabras, JLS exige que los dos sean equivalentes. En teoría, eso podría significar diferencias marginales en el código de bytes, pero en realidad se requiere el bucle mejorado para:
.iterator()
método.hasNext()
.next()
En otras palabras, a todos los efectos prácticos, el código de bytes será idéntico o casi idéntico. Es difícil imaginar una implementación del compilador que resulte en una diferencia significativa entre los dos.
fuente
El
foreach
underhood está creandoiterator
, llamando a hasNext () y llamando a next () para obtener el valor; El problema con el rendimiento solo se produce si está utilizando algo que implementa RandomomAccess.Los problemas de rendimiento con el bucle basado en iterador se deben a que es:
Iterator<CustomObj> iter = customList.iterator();
);iter.hasNext()
Durante cada iteración del bucle hay una llamada virtual invokeInterface (revise todas las clases, luego realice la búsqueda de la tabla de métodos antes del salto).hasNext()
cifra sea el valor: # 1 obtiene el conteo actual y # 2 obtiene el conteo totaliter.next
(así que: revisa todas las clases y realiza una búsqueda en la tabla de métodos antes del salto) y también tiene que hacer la búsqueda de campos: # 1 obtiene el índice y # 2 obtiene la referencia a la matriz para hacer el desplazamiento en él (en cada iteración).Una posible optimización es cambiar a una
index iteration
con la búsqueda de tamaño en caché:Aquí tenemos:
customList.size()
en la creación inicial del bucle for para obtener el tamañocustomList.get(x)
durante el ciclo for body, que es una búsqueda de campo en la matriz y luego puede hacer el desplazamiento en la matrizRedujimos un montón de llamadas a métodos, búsquedas de campo. Esto no quiere hacer con
LinkedList
o con algo que no sea unRandomAccess
obj de colección, de lo contrariocustomList.get(x)
se convertirá en algo que tiene que atravesarloLinkedList
en cada iteración.Esto es perfecto cuando sabes que es una
RandomAccess
colección de listas basada.fuente
foreach
usa iteradores debajo del capó de todos modos. Realmente es solo azúcar sintáctico.Considere el siguiente programa:
Vamos a compilarlo
javac Whatever.java
,y leer el bytecode desmontado de
main()
, usandojavap -c Whatever
:Podemos ver que se
foreach
compila en un programa que:List.iterator()
Iterator.hasNext()
: invocaIterator.next()
y continúa el bucleEn cuanto a "¿por qué este bucle inútil no se optimiza a partir del código compilado? Podemos ver que no hace nada con el elemento de la lista": bueno, es posible que codifique su iterable de modo que
.iterator()
tenga efectos secundarios , o eso.hasNext()
tiene efectos secundarios o consecuencias significativas.Podrías imaginar fácilmente que un iterable que representa una consulta desplazable desde una base de datos podría hacer algo dramático
.hasNext()
(como contactar con la base de datos o cerrar un cursor porque has llegado al final del conjunto de resultados).Entonces, aunque podemos probar que no sucede nada en el cuerpo del bucle ... es más costoso (¿intratable?) Probar que no sucede nada significativo / consecuente cuando iteramos. El compilador tiene que dejar este cuerpo de bucle vacío en el programa.
Lo mejor que podríamos esperar sería una advertencia del compilador . Es interesante que
javac -Xlint:all Whatever.java
no no nos advierten acerca de este cuerpo del bucle vacío. Sin embargo, IntelliJ IDEA lo hace. Es cierto que he configurado IntelliJ para usar Eclipse Compiler, pero esa puede no ser la razón.fuente
Iterator es una interfaz en el marco de colecciones de Java que proporciona métodos para recorrer o iterar sobre una colección.
Tanto el iterador como el bucle actúan de manera similar cuando su motivo es atravesar una colección para leer sus elementos.
for-each
es solo una forma de iterar sobre la Colección.Por ejemplo:
Y for-each loop puede usarse solo en objetos que implementan la interfaz iteradora.
Ahora volvamos al caso de for loop e iterator.
La diferencia se produce cuando intenta modificar una colección. En este caso, el iterador es más eficiente debido a su propiedad de falla rápida . es decir. comprueba cualquier modificación en la estructura de la colección subyacente antes de iterar sobre el siguiente elemento. Si se encuentran modificaciones, arrojará la excepción ConcurrentModificationException .
(Nota: esta funcionalidad de iterador solo es aplicable en el caso de las clases de colección en el paquete java.util. No es aplicable para las colecciones concurrentes ya que son a prueba de fallas por naturaleza)
fuente
Debemos evitar usar el bucle for tradicional mientras trabajamos con Colecciones. La razón simple que daré es que la complejidad del ciclo for es del orden O (sqr (n)) y la complejidad de Iterator o incluso el ciclo for mejorado es solo O (n). Por lo tanto, ofrece una diferencia de rendimiento. Simplemente tome una lista de unos 1000 elementos e imprímala de ambas maneras. y también imprime la diferencia horaria para la ejecución. Puedes ver la diferencia.
fuente