Qual é a maneira mais eficiente de percorrer uma coleção?
List<Integer> a = new ArrayList<Integer>();
for (Integer integer : a) {
integer.toString();
}
ou
List<Integer> a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();) {
Integer integer = (Integer) iterator.next();
integer.toString();
}
Por favor, note que este não é uma cópia exata do presente , presente , presente , ou esta , apesar de uma das respostas à última pergunta chega perto. A razão pela qual isso não é uma bobagem é que a maioria deles está comparando loops onde você chama get(i)
dentro do loop, em vez de usar o iterador.
Conforme sugerido no Meta , postarei minha resposta a esta pergunta.
java
collections
foreach
Paul Wagland
fonte
fonte
Respostas:
Se você está apenas vagando pela coleção para ler todos os valores, não há diferença entre usar um iterador ou a nova sintaxe de loop for, pois a nova sintaxe apenas usa o iterador debaixo d'água.
Se, no entanto, você quer dizer com loop o antigo loop "estilo c":
Então o novo loop for, ou iterador, pode ser muito mais eficiente, dependendo da estrutura de dados subjacente. A razão para isso é que, para algumas estruturas de dados,
get(i)
é uma operação O (n), que torna o loop uma operação O (n 2 ). Uma lista vinculada tradicional é um exemplo dessa estrutura de dados. Todos os iteradores têm como requisito fundamental quenext()
deve ser uma operação O (1), fazendo o loop O (n).Para verificar se o iterador é usado debaixo d'água pela nova sintaxe de loop for, compare os bytecodes gerados a partir dos dois snippets Java a seguir. Primeiro o loop for:
E segundo, o iterador:
Como você pode ver, o código de bytes gerado é efetivamente idêntico; portanto, não há penalidade de desempenho ao usar qualquer um dos formulários. Portanto, você deve escolher a forma de loop mais esteticamente atraente para você, para a maioria das pessoas que será o loop for-each, pois possui menos código padrão.
fonte
for(int i; i < list.size(); i++) {
loop de estilo também deve ser avaliadolist.size()
no final de cada iteração; se usado, às vezes é mais eficiente armazenar em cache o resultadolist.size()
primeiro.A diferença não está no desempenho, mas na capacidade. Ao usar uma referência diretamente, você tem mais poder sobre explicitamente o uso de um tipo de iterador (por exemplo, List.iterator () vs. List.listIterator (), embora na maioria dos casos eles retornem a mesma implementação). Você também pode referenciar o iterador em seu loop. Isso permite que você faça coisas como remover itens da sua coleção sem obter uma ConcurrentModificationException.
por exemplo
Está tudo bem:
Isso não é, pois lançará uma exceção de modificação simultânea:
fonte
Para expandir a resposta de Paul, ele demonstrou que o bytecode é o mesmo naquele compilador específico (presumivelmente javac da Sun?), Mas não é garantido que diferentes compiladores gerem o mesmo bytecode, certo? Para ver qual é a diferença real entre os dois, vamos direto à fonte e verifique a Especificação da Linguagem Java, especificamente 14.14.2, "A declaração aprimorada" :
Em outras palavras, é exigido pelo JLS que os dois sejam equivalentes. Em teoria, isso pode significar diferenças marginais no código de bytes, mas na realidade o loop for aprimorado é necessário para:
.iterator()
método.hasNext()
.next()
Portanto, em outras palavras, para todos os fins práticos, o bytecode será idêntico ou quase idêntico. É difícil imaginar qualquer implementação de compilador que resultaria em qualquer diferença significativa entre os dois.
fonte
A parte
foreach
inferior está criando oiterator
, chamando hasNext () e chamando next () para obter o valor; O problema com o desempenho ocorre apenas se você estiver usando algo que implemente o RandomomAccess.Os problemas de desempenho com o loop baseado no iterador são:
Iterator<CustomObj> iter = customList.iterator();
);iter.hasNext()
durante cada iteração do loop, há uma chamada virtual invokeInterface (percorra todas as classes e faça a pesquisa da tabela de métodos antes do salto).hasNext()
o valor da chamada o valor: # 1 obter contagem atual e # 2 obter contagem totaliter.next
(assim: percorra todas as classes e faça a pesquisa da tabela de métodos antes do salto) e também faça a pesquisa de campos: # 1 obtenha o índice e # 2 obtenha a referência ao array para fazer o deslocamento nele (em todas as iterações).Uma otimização potencial é mudar para um
index iteration
com a pesquisa de tamanho em cache:Aqui temos:
customList.size()
na criação inicial do loop for para obter o tamanhocustomList.get(x)
durante o loop for do corpo, que é uma pesquisa de campo na matriz e, em seguida, pode fazer o deslocamento na matrizReduzimos uma tonelada de chamadas de método, pesquisas de campo. Isso você não quer fazer com
LinkedList
ou com algo que não seja umRandomAccess
objeto de coleção, caso contrário, elecustomList.get(x)
se transformará em algo que terá que atravessar aLinkedList
cada iteração.Isso é perfeito quando você sabe que existe qualquer
RandomAccess
coleção de listas baseada.fonte
foreach
usa iteradores sob o capô de qualquer maneira. Realmente é apenas açúcar sintático.Considere o seguinte programa:
Vamos compilá-lo com
javac Whatever.java
,e ler o bytecode desmontado de
main()
, usandojavap -c Whatever
:Podemos ver que é
foreach
compilado em um programa que:List.iterator()
Iterator.hasNext()
: chamaIterator.next()
e continua o loopQuanto a "por que esse loop inútil não é otimizado a partir do código compilado? Podemos ver que ele não faz nada com o item da lista": bem, é possível codificar seu iterável de modo que
.iterator()
tenha efeitos colaterais , ou que.hasNext()
tenha efeitos colaterais ou consequências significativas.Você pode imaginar facilmente que uma iterável que representa uma consulta rolável de um banco de dados pode fazer algo dramático
.hasNext()
(como entrar em contato com o banco de dados ou fechar um cursor porque você atingiu o final do conjunto de resultados).Portanto, mesmo que possamos provar que nada acontece no corpo do loop ... é mais caro (intratável?) Provar que nada significativo / conseqüente acontece quando iteramos. O compilador deve deixar esse corpo de loop vazio no programa.
O melhor que poderíamos esperar seria um aviso do compilador . É interessante que
javac -Xlint:all Whatever.java
faz não avisar-nos sobre este corpo de loop vazio. O IntelliJ IDEA faz isso. É certo que eu configurei o IntelliJ para usar o Eclipse Compiler, mas talvez não seja esse o motivo.fonte
O iterador é uma interface na estrutura Java Collections que fornece métodos para percorrer ou iterar sobre uma coleção.
O iterador e o loop for agem de maneira semelhante quando seu motivo é apenas percorrer uma coleção para ler seus elementos.
for-each
é apenas uma maneira de iterar sobre a coleção.Por exemplo:
E para cada loop pode ser usado apenas em objetos que implementam a interface do iterador.
Agora, de volta ao caso do loop e iterador for.
A diferença ocorre quando você tenta modificar uma coleção. Nesse caso, o iterador é mais eficiente devido à sua propriedade à prova de falhas . ie ele verifica qualquer modificação na estrutura da coleção subjacente antes de iterar sobre o próximo elemento. Se houver alguma modificação encontrada, ele lançará o ConcurrentModificationException .
(Nota: Essa funcionalidade do iterador é aplicável apenas no caso de classes de coleção no pacote java.util. Não é aplicável a coleções simultâneas, pois elas são à prova de falhas por natureza)
fonte
Devemos evitar o uso do loop for tradicional ao trabalhar com o Collections. A razão simples que mostrarei é que a complexidade do loop for é da ordem O (sqr (n)) e a complexidade do Iterator ou mesmo o loop for aprimorado é apenas O (n). Portanto, há uma diferença de desempenho. Basta pegar uma lista de 1000 itens e imprimi-la usando os dois lados. e também imprima a diferença de horário para a execução. Você pode ver a diferença.
fonte