컬렉션을 탐색하는 가장 효율적인 방법은 무엇입니까?
List<Integer> a = new ArrayList<Integer>();
for (Integer integer : a) {
integer.toString();
}
또는
List<Integer> a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();) {
Integer integer = (Integer) iterator.next();
integer.toString();
}
마지막 질문에 대한 답변 중 하나가 가까이 오지만 이것은 this , this , this 또는 this 의 정확한 사본이 아닙니다 . 이것이 get(i)
속이 아닌 이유는 대부분 반복자를 사용하는 대신 루프 내부에서 호출 하는 루프 를 비교 하기 때문입니다.
답변
모든 값을 읽기 위해 컬렉션을 방황하고 있다면 새로운 구문은 수 중에서 반복자를 사용하기 때문에 반복자를 사용하는 것과 새로운 for 루프 구문을 사용하는 것에는 차이가 없습니다.
그러나 이전 “c- 스타일”루프를 반복한다는 의미입니다.
for(int i=0; i<list.size(); i++) {
Object o = list.get(i);
}
그러면 새로운 for 루프 또는 반복자가 기본 데이터 구조에 따라 훨씬 더 효율적일 수 있습니다. 그 이유는 일부 데이터 구조의 get(i)
경우 루프를 O (n 2 ) 연산으로 만드는 O (n) 연산이기 때문입니다 . 전통적인 링크리스트는 이러한 데이터 구조의 예입니다. 모든 반복자는 next()
루프 O (n)을 만드는 O (1) 연산이어야 하는 기본 요구 사항 이 있습니다.
새로운 for 루프 구문에서 반복자가 수 중에서 사용되는지 확인하려면 다음 두 Java 스 니펫에서 생성 된 바이트 코드를 비교하십시오. 먼저 for 루프 :
List<Integer> a = new ArrayList<Integer>();
for (Integer integer : a)
{
integer.toString();
}
// Byte code
ALOAD 1
INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;
ASTORE 3
GOTO L2
L3
ALOAD 3
INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object;
CHECKCAST java/lang/Integer
ASTORE 2
ALOAD 2
INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String;
POP
L2
ALOAD 3
INVOKEINTERFACE java/util/Iterator.hasNext()Z
IFNE L3
둘째, 반복자 :
List<Integer> a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();)
{
Integer integer = (Integer) iterator.next();
integer.toString();
}
// Bytecode:
ALOAD 1
INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;
ASTORE 2
GOTO L7
L8
ALOAD 2
INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object;
CHECKCAST java/lang/Integer
ASTORE 3
ALOAD 3
INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String;
POP
L7
ALOAD 2
INVOKEINTERFACE java/util/Iterator.hasNext()Z
IFNE L8
보시다시피, 생성 된 바이트 코드는 사실상 동일하므로 두 형식 중 하나를 사용하면 성능이 저하되지 않습니다. 따라서 보일러 플레이트 코드가 적기 때문에 for-each 루프가 될 대부분의 사람들에게는 가장 미적으로 매력적인 루프 형식을 선택해야합니다.
답변
차이점은 성능이 아니라 기능입니다. 참조를 직접 사용하는 경우 반복자 유형 (예 : List.iterator () 대 List.listIterator ())을 명시 적으로 사용하는 것보다 더 많은 권한이 있습니다 (대부분의 경우 동일한 구현을 리턴하지만). 루프에서 반복자를 참조하는 기능도 있습니다. 이를 통해 ConcurrentModificationException을받지 않고 컬렉션에서 항목을 제거하는 등의 작업을 수행 할 수 있습니다.
예 :
괜찮습니다 :
Set<Object> set = new HashSet<Object>();
// add some items to the set
Iterator<Object> setIterator = set.iterator();
while(setIterator.hasNext()){
Object o = setIterator.next();
if(o meets some condition){
setIterator.remove();
}
}
동시 수정 예외가 발생하므로 그렇지 않습니다.
Set<Object> set = new HashSet<Object>();
// add some items to the set
for(Object o : set){
if(o meets some condition){
set.remove(o);
}
}
답변
Paul의 대답을 확장하기 위해 바이트 코드가 특정 컴파일러 (예 : Sun의 javac?) 에서 동일 하지만 다른 컴파일러가 동일한 바이트 코드를 생성 한다고 보장 하지는 않습니다 . 이 둘의 실제 차이점을 확인하려면 소스로 가서 Java 언어 사양, 특히 14.14.2, “향상된 구문”을 확인하십시오 .
향상된
for
설명은for
다음 형식 의 기본 설명과 같습니다.
for (I #i = Expression.iterator(); #i.hasNext(); ) {
VariableModifiers(opt) Type Identifier = #i.next();
Statement
}
다시 말해, JLS는 두 개가 동등해야합니다. 이론적으로 바이트 코드의 한계 차이를 의미 할 수 있지만 실제로는 향상된 for 루프가 다음을 수행해야합니다.
.iterator()
메소드를 호출하십시오.- 사용하다
.hasNext()
- 를 통해 지역 변수를 사용할 수있게하십시오
.next()
즉, 모든 실제적인 목적으로 바이트 코드는 동일하거나 거의 동일합니다. 컴파일러 구현을 구상하기는 어렵 기 때문에이 둘 사이에 큰 차이가 생길 수 있습니다.
답변
기본 foreach
은을 작성하고iterator
hasNext ()를 호출하고 next ()를 호출하여 값을 가져옵니다. 성능 문제는 RandomomAccess를 구현하는 것을 사용하는 경우에만 발생합니다.
for (Iterator<CustomObj> iter = customList.iterator(); iter.hasNext()){
CustomObj custObj = iter.next();
....
}
반복자 기반 루프의 성능 문제는 다음과 같은 이유 때문입니다.
- 리스트가 비어 있어도 객체를 할당하는 단계 (
Iterator<CustomObj> iter = customList.iterator();
); iter.hasNext()
루프를 반복 할 때마다 invokeInterface 가상 호출이 있습니다 (모든 클래스를 거치고 점프 전에 메소드 테이블 조회를 수행하십시오).- 반복자의 구현은
hasNext()
콜 수치를 값 으로 만들기 위해 적어도 2 개의 필드 조회를 수행해야 합니다. - 본문 루프 안에 또 다른 invokeInterface 가상 호출이 있습니다
iter.next
(따라서 점프하기 전에 모든 클래스를 거치고 메소드 테이블 조회를 수행하십시오). 또한 필드 조회를 수행해야합니다. (반복마다) 오프셋을 수행하는 배열.
잠재적 인 최적화는index iteration
캐시 된 크기 조회 로로 전환하는 것 입니다.
for(int x = 0, size = customList.size(); x < size; x++){
CustomObj custObj = customList.get(x);
...
}
여기에 우리가 있습니다 :
customList.size()
for 루프의 초기 생성시 크기를 얻기위한 하나의 invokeInterface 가상 메소드 호출customList.get(x)
본문 for 루프 동안 get 메소드 호출 . 배열에 대한 필드 조회이며 배열에 대한 오프셋을 수행 할 수 있습니다.
우리는 수많은 메소드 호출, 필드 조회를 줄였습니다. 이것은 컬렉션 obj LinkedList
가 아닌 무언가 와 관련이 있거나 원하지 RandomAccess
않는 경우, 그렇지 않으면 모든 반복 customList.get(x)
에서 통과 해야하는 것으로 바뀔 것입니다 LinkedList
.
이것이 RandomAccess
기반 목록 모음 임을 알 때 완벽 합니다.
답변
foreach
어쨌든 후드 아래에서 반복자를 사용합니다. 그것은 단지 구문 설탕입니다.
다음 프로그램을 고려하십시오.
import java.util.List;
import java.util.ArrayList;
public class Whatever {
private final List<Integer> list = new ArrayList<>();
public void main() {
for(Integer i : list) {
}
}
}
의와 함께 컴파일하자 javac Whatever.java
,
그리고의 분해 바이트 코드를 읽어 main()
사용 javap -c Whatever
:
public void main();
Code:
0: aload_0
1: getfield #4 // Field list:Ljava/util/List;
4: invokeinterface #5, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
9: astore_1
10: aload_1
11: invokeinterface #6, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
16: ifeq 32
19: aload_1
20: invokeinterface #7, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
25: checkcast #8 // class java/lang/Integer
28: astore_2
29: goto 10
32: return
우리는 foreach
다음과 같은 프로그램으로 컴파일 되는 것을 볼 수 있습니다 .
- 다음을 사용하여 반복자를 만듭니다.
List.iterator()
- If
Iterator.hasNext()
:Iterator.next()
루프를 호출 하고 계속합니다
에 관해서는? “이 쓸모없는 루프가 컴파일 된 코드의 밖으로 최적화되지 않는 이유를 우리는 목록 항목으로 아무것도하지 않는 것을 볼 수있다”음, 코드 당신의 반복 가능한 그런 당신을 위해 가능 .iterator()
부작용이있다 또는 .hasNext()
부작용 또는 의미있는 결과가있는 것입니다.
데이터베이스에서 스크롤 가능한 쿼리를 나타내는 iterable .hasNext()
이 데이터베이스에 접속하거나 결과 세트의 끝에 도달하여 커서를 닫는 것과 같이 극적인 일을 할 수 있다고 쉽게 상상할 수 있습니다 .
따라서 루프 본문에서 아무 일도 일어나지 않는다는 것을 증명할 수 있지만 반복 할 때 의미 있고 결과적인 일이 발생하지 않음을 증명하는 것이 더 비쌉니다. 컴파일러는이 빈 루프 본문을 프로그램에 그대로 두어야합니다.
우리가 기대할 수있는 최선은 컴파일러 경고 일 것 입니다. 이 빈 루프 바디에 대해 경고 javac -Xlint:all Whatever.java
하지 않는 것이 흥미 롭습니다 . 그러나 IntelliJ IDEA는 그렇게합니다. 필자는 Eclipse 컴파일러를 사용하도록 IntelliJ를 구성했지만 그 이유가 아닐 수도 있습니다.
답변
반복자는 컬렉션을 순회하거나 반복하는 메소드를 제공하는 Java Collections 프레임 워크의 인터페이스입니다.
반복자와 for 루프는 컬렉션을 가로 질러 해당 요소를 읽으려고 할 때 비슷하게 작동합니다.
for-each
컬렉션을 반복하는 한 가지 방법입니다.
예를 들면 다음과 같습니다.
List<String> messages= new ArrayList<>();
//using for-each loop
for(String msg: messages){
System.out.println(msg);
}
//using iterator
Iterator<String> it = messages.iterator();
while(it.hasNext()){
String msg = it.next();
System.out.println(msg);
}
for-each 루프는 반복자 인터페이스를 구현하는 객체에서만 사용할 수 있습니다.
이제 for 루프와 반복자의 경우로 돌아갑니다.
컬렉션을 수정하려고 할 때 차이점이 있습니다. 이 경우 iterator는 fail-fast 특성 으로 인해 더 효율적 입니다. 즉. 다음 요소를 반복하기 전에 기본 컬렉션 구조의 수정 사항을 확인합니다. 수정 사항이 있으면 ConcurrentModificationException이 발생 합니다.
(참고 :이 반복자의 기능은 java.util 패키지의 콜렉션 클래스의 경우에만 적용 가능합니다. 동시 콜렉션에는 본질적으로 페일 세이프하므로 적용 할 수 없습니다)
답변
컬렉션으로 작업하는 동안 전통적인 for 루프를 사용하지 않아야합니다. 내가 줄 간단한 이유는 for 루프의 복잡성이 O (sqr (n))이고 Iterator의 복잡성 또는 향상된 for 루프가 O (n)에 불과하기 때문입니다. 따라서 성능 차이가 발생합니다. 1000 개 정도의 항목 목록을 가져 와서 두 가지 방법으로 인쇄하십시오. 또한 실행 시차를 인쇄합니다. 차이점을 볼 수 있습니다.