[java] Java의 배열 또는 목록 어느 것이 더 빠릅니까?

Java에서 직렬로 액세스하려면 메모리에 수천 개의 문자열을 유지해야합니다. 배열에 저장해야합니까, 아니면 일종의 List를 사용해야합니까?

배열은 List와 달리 모든 데이터를 인접한 메모리 덩어리에 보관하므로 배열을 사용하여 수천 개의 문자열을 저장하면 문제가 발생합니까?



답변

프로파일 러를 사용하여 어느 것이 더 빠른지 테스트하는 것이 좋습니다.

내 개인적인 의견은 당신이 목록을 사용해야한다는 것입니다.

나는 큰 코드베이스에서 일하고 있으며 이전 개발자 그룹은 어디서나 배열을 사용했다 . 코드를 매우 유연하게 만들었습니다. 큰 덩어리를 Lists로 변경 한 후에 속도 차이가 없었습니다.


답변

Java 방식은 요구 사항에 가장 적합한 데이터 추상화를 고려해야 합니다. Java에서 목록은 구체적인 데이터 유형이 아니라 추상이라는 것을 기억하십시오. 문자열을 List로 선언 한 다음 ArrayList 구현을 사용하여 초기화해야합니다.

List<String> strings = new ArrayList<String>();

이러한 추상 데이터 유형과 특정 구현의 분리는 객체 지향 프로그래밍의 주요 측면 중 하나입니다.

ArrayList는 배열을 기본 구현으로 사용하여 List Abstract Data Type을 구현합니다. 액세스 속도는 실제로 배열과 동일하며, 요소를 List에 추가하거나 빼는 이점 (ArrayList를 사용한 O (n) 연산 임)과 나중에 기본 구현을 변경하기로 결정한 경우의 추가 이점이 있습니다. 당신은 할 수 있습니다. 예를 들어, 동기화 된 액세스가 필요한 경우 모든 코드를 다시 작성하지 않고 구현을 Vector로 변경할 수 있습니다.

실제로 ArrayList는 대부분의 컨텍스트에서 하위 수준 배열 구문을 대체하도록 특별히 설계되었습니다. 오늘날 Java를 설계했다면 ArrayList 구문을 선호하여 배열을 완전히 생략했을 가능성이 있습니다.

배열은 List와 달리 모든 데이터를 인접한 메모리 덩어리에 보관하므로 배열을 사용하여 수천 개의 문자열을 저장하면 문제가 발생합니까?

Java에서 모든 콜렉션은 오브젝트 자체가 아닌 오브젝트에 대한 참조 만 저장합니다. 배열과 ArrayList는 연속 배열에 수천 개의 참조를 저장하므로 본질적으로 동일합니다. 현대 하드웨어에서는 수천 개의 32 비트 참조로 이루어진 연속 블록을 항상 쉽게 사용할 수 있다고 생각할 수 있습니다. 그렇다고해서 연속적인 메모리 블록 요구 사항을 충족시키기가 어렵다는 것만으로도 메모리 부족을 보장 할 수는 없습니다.


답변

ArrayList 사용을 제안하는 답변은 대부분의 시나리오에서 의미가 있지만 상대 성능에 대한 실제 질문은 실제로 답변되지 않았습니다.

배열로 할 수있는 일이 몇 가지 있습니다 :

  • 그것을 만들
  • 아이템을 설정하다
  • 아이템을 얻다
  • 복제 / 복사

일반적인 결론

ArrayList에서는 get 및 set 작업이 다소 느리지 만 (내 컴퓨터에서 호출 당 1 및 3 나노초에 해당) 집중적이지 않은 용도로 ArrayList와 배열을 사용하는 오버 헤드는 거의 없습니다. 그러나 명심해야 할 것이 몇 가지 있습니다.

  • 목록의 크기 조정 작업 (호출시 list.add(...))은 비용이 많이 들고 가능한 경우 초기 용량을 적절한 수준으로 설정해야합니다 (어레이를 사용할 때도 동일한 문제가 발생 함)
  • 프리미티브를 처리 할 때 많은 복싱 / 언 박싱 변환을 피할 수 있으므로 배열이 훨씬 빠릅니다.
  • ArrayList의 값만 가져 오거나 설정하는 응용 프로그램 (매우 드물지 않음)은 배열로 전환하여 25 % 이상의 성능 향상을 볼 수 있습니다

자세한 결과

다음은 표준 x86 데스크톱 시스템에서 JDK 7과 함께 jmh 벤치마킹 라이브러리 (나노초 단위)를 사용하여 세 가지 작업에 대해 측정 한 결과 입니다. 테스트에서 ArrayList의 크기를 조정하여 결과를 비교할 수 없도록하십시오. 벤치 마크 코드는 여기에 있습니다 .

배열 / 배열 목록 생성

나는 다음 문장을 실행하면서 4 가지 테스트를 실행했습니다.

  • createArray1 : Integer[] array = new Integer[1];
  • createList1 : List<Integer> list = new ArrayList<> (1);
  • createArray10000 : Integer[] array = new Integer[10000];
  • createList10000 : List<Integer> list = new ArrayList<> (10000);

결과 (통화 당 나노초, 95 % 신뢰도) :

a.p.g.a.ArrayVsList.CreateArray1         [10.933, 11.097]
a.p.g.a.ArrayVsList.CreateList1          [10.799, 11.046]
a.p.g.a.ArrayVsList.CreateArray10000    [394.899, 404.034]
a.p.g.a.ArrayVsList.CreateList10000     [396.706, 401.266]

결론 : 눈에 띄는 차이가 없습니다 .

수술을 받다

나는 다음 문장을 실행하면서 2 가지 테스트를 실행했다.

  • getList : return list.get(0);
  • getArray : return array[0];

결과 (통화 당 나노초, 95 % 신뢰도) :

a.p.g.a.ArrayVsList.getArray   [2.958, 2.984]
a.p.g.a.ArrayVsList.getList    [3.841, 3.874]

결론 : 배열에서 얻는 것이 ArrayList에서 얻는 보다 약 25 % 빠르지 만 차이는 1 나노초 정도입니다.

작업 설정

나는 다음 문장을 실행하면서 2 가지 테스트를 실행했다.

  • setList : list.set(0, value);
  • setArray : array[0] = value;

결과 (통화 당 나노초) :

a.p.g.a.ArrayVsList.setArray   [4.201, 4.236]
a.p.g.a.ArrayVsList.setList    [6.783, 6.877]

결론 : 배열의 집합 연산은 목록보다 약 40 % 빠릅니다 . 그러나 get과 관련하여 각 집합 연산은 몇 나노 초가 걸립니다. 차이가 1 초에 도달하려면 목록 / 배열 백에 항목을 설정해야합니다. 수백만 번!

복제 / 복사

행의 ArrayList를 복사 생성자 위임 Arrays.copyOf성능 어레이 복사본과 동일한 정도로는 (비아 어레이를 복사 clone, Arrays.copyOf또는 System.arrayCopy 어떠한 물질 차이 심하고하지 않는다 ).


답변

배열보다 제네릭 형식을 선호해야합니다. 다른 사람들이 언급했듯이 배열은 융통성이 없으며 일반적인 유형의 표현력이 없습니다. (그러나 런타임 유형 검사는 지원하지만 일반 유형과 잘못 혼합됩니다.)

그러나 항상 그렇듯이 최적화 할 때는 항상 다음 단계를 수행해야합니다.

  • 훌륭하고 깨끗하며 작동하는 코드 버전이 될 때까지 최적화하지 마십시오 . 이 단계에서 제네릭 형식으로 변경하면 동기가 생길 수 있습니다.
  • 멋지고 깨끗한 버전이 있으면 충분히 빠른지 결정하십시오.
  • 속도가 충분하지 않으면 성능을 측정하십시오 . 이 단계는 두 가지 이유로 중요합니다. 측정하지 않으면 (1) 최적화의 영향을 알지 못하고 (2) 최적화 할 위치를 알 수 없습니다.
  • 코드에서 가장 인기있는 부분을 최적화하십시오.
  • 다시 측정하십시오. 이것은 전에 측정하는 것만 큼 중요합니다. 최적화로 문제가 해결되지 않으면 되돌립니다 . 최적화가 없는 코드 는 깨끗하고 훌륭하며 작동했습니다.

답변

원래 포스터가 C ++ / STL 배경에서 나온 것으로 추측되어 혼란을 겪고 있습니다. C ++에서는 std::list이중 연결 목록입니다.

Java에서는 [java.util.]List구현이 필요없는 인터페이스입니다 (C ++ 용어의 순수한 추상 클래스). List이중 연결 목록이 될 수 있습니다- java.util.LinkedList제공됩니다. 그러나 새로 만들기를 원할 때 100에서 99 시간이 99 번 대신 C ++과 거의 동일한 List것을 사용하려고합니다 . 및에 의해 반환되는 것과 같은 다른 표준 구현이 있습니다 .java.util.ArrayListstd::vectorjava.util.Collections.emptyList()java.util.Arrays.asList()

성능 관점에서 인터페이스와 추가 객체를 통과해야하는 데는 약간의 히트가 있지만 런타임 인라이닝은 거의 의미가 없습니다. 또한 String일반적으로 객체와 배열 이라는 것을 기억하십시오 . 따라서 각 항목마다 두 개의 다른 객체가있을 수 있습니다. C ++에서는 std::vector<std::string>포인터없이 값으로 복사하지만 문자 배열은 문자열의 객체를 형성합니다 (일반적으로 공유되지는 않습니다).

이 특정 코드가 실제로 성능에 민감한 경우 모든 문자열의 모든 문자에 대해 단일 char[]배열 (또는 byte[])을 만든 다음 오프셋 배열을 만들 수 있습니다. IIRC, 이것이 javac가 구현되는 방식입니다.


답변

대부분의 경우 배열보다 ArrayLists의 유연성과 우아함을 선택해야하며, 대부분의 경우 프로그램 성능에 미치는 영향은 무시할 수 있다는 데 동의합니다.

그러나 소프트웨어 그래픽 렌더링 또는 사용자 지정 가상 시스템과 같은 구조적 변경 (추가 및 제거 없음)이 거의없는 지속적이고 무거운 반복 작업을 수행하는 경우 순차적 액세스 벤치마킹 테스트에서 ArrayList가배열보다 1.5 배 느립니다. 시스템 (1 년 된 iMac의 Java 1.6).

일부 코드 :

import java.util.*;

public class ArrayVsArrayList {
    static public void main( String[] args ) {

        String[] array = new String[300];
        ArrayList<String> list = new ArrayList<String>(300);

        for (int i=0; i<300; ++i) {
            if (Math.random() > 0.5) {
                array[i] = "abc";
            } else {
                array[i] = "xyz";
            }

            list.add( array[i] );
        }

        int iterations = 100000000;
        long start_ms;
        int sum;

        start_ms = System.currentTimeMillis();
        sum = 0;

        for (int i=0; i<iterations; ++i) {
          for (int j=0; j<300; ++j) sum += array[j].length();
        }

        System.out.println( (System.currentTimeMillis() - start_ms) + " ms (array)" );
        // Prints ~13,500 ms on my system

        start_ms = System.currentTimeMillis();
        sum = 0;

        for (int i=0; i<iterations; ++i) {
          for (int j=0; j<300; ++j) sum += list.get(j).length();
        }

        System.out.println( (System.currentTimeMillis() - start_ms) + " ms (ArrayList)" );
        // Prints ~20,800 ms on my system - about 1.5x slower than direct array access
    }
}


답변

우선 고전적인 compsci 데이터 구조 의미에서 “목록”을 의미합니까 (즉, 연결된 목록) java.util.List를 의미합니까? java.util.List를 의미하는 경우 인터페이스입니다. 배열을 사용하려면 ArrayList 구현을 사용하면 배열과 유사한 동작 및 의미를 얻을 수 있습니다. 문제 해결됨.

링크 된 목록 대 배열을 의미하는 경우, 그것은 우리가 여기 (빅 O 다시 가서되는 약간 다른 인자의 일반 영어 설명 이 생소한 용어 인 경우.

정렬;

  • 랜덤 액세스 : O (1);
  • 삽입 : O (n);
  • 삭제 : O (n).

연결된 목록 :

  • 랜덤 액세스 : O (n);
  • 삽입 : O (1);
  • 삭제 : O (1).

따라서 어레이의 크기를 조정하는 방법에 가장 적합한 것을 선택하십시오. 크기를 조정하고 많이 삽입하고 삭제하면 연결된 목록이 더 나은 선택 일 수 있습니다. 랜덤 액세스가 드물 경우에도 마찬가지입니다. 직렬 액세스에 대해 언급했습니다. 수정이 거의없이 주로 직렬 액세스를 수행하는 경우 선택하는 것이 중요하지 않습니다.

연결 된 목록은 잠재적으로 비 연속적 인 메모리 블록과 다음 요소에 대한 (효과적으로) 포인터를 다루기 때문에 약간 더 높은 오버 헤드가 있습니다. 그러나 수백만 개의 항목을 다루지 않는 한 중요한 요소는 아닙니다.