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


351

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

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


5
"배열은 모든 데이터를 인접한 메모리 덩어리에 보관하기 때문에"Java를 위해이를 백업하기위한 인용이 있습니까?
matt b

1
매트가 없습니다. 나는 이것을 C에 대해 알고있다. 나는 자바가 같은 방법을 사용할 것이라고 추측하고있다.
euphoria83

그것들이 단일 메모리 덩어리에 보관 될 것이라고 의심합니다.
Fortyrunner 2009

3
단일 메모리 블록 인 경우에도 여전히 1000 * 4 = 4kb에 불과하며 많은 메모리가 아닙니다.
CookieOfFortune 2009

3
@mattb CS 전체에서 '어레이'가 의미하는 바입니다. 인용이 필요하지 않습니다. 배열 길이 에 대한 JLS 및 [JVM Spec] () 의 수많은 참조는 배열이 연속적인 경우에만 이해할 수 있습니다.
Lorne의 후작

답변:


358

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

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

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


2
@Fortyrunner-경험에 비추어 볼 때 Java에서 추상화와 원시 데이터 형식 사이에서 성능에 큰 차이를 만드는 선택 사항이 있습니까?
euphoria83

4
성능 측정 문제 중 하나는 새로운 버전의 Java에 대해 지속적으로 다시 테스트해야한다는 것입니다. 누군가가 맵의 키를 위해 int를 사용하여 (공간 / 시간을 절약하는) 순간에 문제를 해결하고 있습니다. 이제 모든 라인을 새로운 객체로 변경해야합니다. 고통 스럽습니다.
Fortyrunner 2009

9
그래서 .. 이제 원시 데이터를 피하려고 노력합니다. 눈에 띄는 차이는 거의 없습니다. 핫스팟은 놀라운 기술이며 절대로 시도하지 말아야합니다. 간단하고 유지 보수가 쉬운 코드를 작성하면 핫스팟이 나머지 작업을 수행합니다.
Fortyrunner 2009

4
프로파일 러 결과는 프로파일 러를 실행중인 Java 플랫폼에만 유효합니다. 고객과 다를 수 있습니다.
Mikkel Løkke

4
효과적인 Java는 API 상호 운용성을 지원하고 유형 안전을 통해 더욱 안전한 목록을 권장합니다.
juanmf

164

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 # ensureCapacity를 사용하는 것이 좋습니다.
JesperE 2009

6
동적 바인딩 비용을 지불하지 않습니까?
Uri

2
예를 들어 용량이 두 배 대신에 단지 1 씩 증가, 두 번 이상 추가 할 때 내가하지 O (n)이 ArrayList의에 추가 추측에는 요, 일부 ammortization 효과가 있어야한다
zedoo

@zedoo 나는 중간에 더하기와 빼기를 의미한다고 생각합니다.
MalcolmOcean

"오늘날 Java를 설계했다면 ArrayList 구문을 선호하여 배열을 완전히 생략했을 가능성이 있습니다." ... 나는 이것이 사실인지 진지하게 의심한다. 오늘날 JVM 을 다시 작성 했다면 분명히 말할 수 있습니다. 그러나 우리가 가지고있는 JVM에서 배열은 Java의 기본 유형입니다.
scottb

100

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
좋은 분석. 그러나, 당신의 의견에 대한 당신은, "그들은 많은 복싱 / 언 박싱 변환을 방지하기 위해 하나를 수로 프리미티브 처리, 배열은 훨씬 빠르게 할 수있다" 당신의 케이크를 가지고 원시적 배열 백업 목록으로 너무 그것을 먹을 이행; 예를 들면 : github.com/scijava/scijava-common/blob/master/src/main/java/org/... . 실제로 그러한 일이 핵심 Java로 만들어지지 않은 것에 놀랐습니다.
ctrueden

2
@ctrueden yes 표준 JDK ArrayList에 주석이 적용되었습니다. trove4j는 기본 목록을 지원하는 잘 알려진 라이브러리입니다. Java 8은 몇 가지 기본 특수 스트림으로 일부 개선되었습니다.
assylias

jmh 벤치 마크가 어떻게 작동하는지 모르지만 발생할 수있는 JIT 컴파일을 고려합니까? JVM이 코드를 컴파일 할 때 Java 애플리케이션의 성능은 시간이 지남에 따라 달라질 수 있습니다.
Hoffmann

@Hoffmann 예-측정에서 제외되는 예열 단계를 포함합니다.
assylias

97

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

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

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

24

원래 포스터가 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가 구현되는 방식입니다.


1
답변 감사합니다. 그러나 아니요, C ++ 목록과 Java의 인터페이스 목록을 혼동하지 않습니다. ArrayList 및 Vector와 같은 List 구현의 성능을 원시 배열과 비교하고 싶기 때문에 그런 식으로 질문했습니다.
euphoria83

ArrayList와 Vector 모두 "모든 데이터를 연속 된 메모리 청크에 보관합니다".
Tom Hawtin-tackline

13

대부분의 경우 배열보다 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
    }
}

나는 이것이 흥미로운 대답을 찾았지만 ArrayList가 메모리의 초기 크기로 초기화되지 않으면 더 나빠질 지 궁금합니다. 일반적으로 어떤 의미에서 네이티브 배열보다 ArrayList를 사용하면 얻을 수있는 이점은 알지 못하고 걱정할 필요가 없다는 것입니다. ArrayList는 기본적으로 초기 길이 10으로 생성 된 다음 크기가 조정됩니다. 크기 조정이 비싸다고 생각합니다. 분명히 벤치마킹을 시도하지 않았습니다.
Zak Patterson 21

4
이 마이크로 벤치 마크에는 결함이 있습니다 (예열하지 않고 별도의 방법으로 작업하지 않으므로 arraylist 부분이 JIT 등으로 최적화되지 않음)
assylias

assylias에 동의합니다. 이 벤치 마크의 결과는 신뢰할 수 없습니다.
Stephen C

@StephenC 적절한 마이크로 벤치 마크를 추가했습니다.
assylias

11

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

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

정렬;

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

연결된 목록 :

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

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

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


내가 의미를 java.util.List 인터페이스
euphoria83

1
링크 된 목록의 임의 액세스 O (n)는 나에게 큰 것처럼 보입니다.
Bjorn

11

ArrayLists와 Arrays를 비교하는 작은 벤치 마크를 작성했습니다. 내 오래된 랩톱에서 5000 요소 배열 목록을 1000 번 통과하는 시간은 동등한 배열 코드보다 약 10 밀리 초 느 렸습니다.

그래서, 당신은 아무것도하지 않고 있지만 목록을 반복, 당신이 그것을 많이하고 일을하고 있다면 아마도 그것의 가치를 최적화합니다. 당신이 때 쉽게 만들 것이기 때문에 그렇지 않으면 나는 목록을 사용하는 거라고 코드를 최적화 할 필요가 있습니다.

nb 나는 구식 for-loop를 사용하여 목록에 액세스하는 것보다 사용 이 약 50 % 느리다는 것을 알았 습니다 for String s: stringsList. Go figure ... 여기에 내가 시간을 정한 두 가지 기능이있다. 배열과 목록은 5000 개의 임의의 (다른) 문자열로 채워졌습니다.

private static void readArray(String[] strings) {
    long totalchars = 0;
    for (int j = 0; j < ITERATIONS; j++) {
        totalchars = 0;
        for (int i = 0; i < strings.length; i++) {
            totalchars += strings[i].length();

        }
    }
}

private static void readArrayList(List<String> stringsList) {
    long totalchars = 0;
    for (int j = 0; j < ITERATIONS; j++) {
        totalchars = 0;
        for (int i = 0; i < stringsList.size(); i++) {
            totalchars += stringsList.get(i).length();
        }
    }
}

@ Chris May : 훌륭합니다! 둘 다에 대한 실제 실행 시간은 얼마입니까? 사용중인 문자열의 크기를 알려주시겠습니까? 또한 'Strings : stringsList'를 사용하면 시간이 오래 걸리므로 일반적으로 Java에서 더 높은 추상화를 사용하는 것이 가장 큰 두려움입니다.
euphoria83

이 mcirobenchmark의 줄 길이는 실제로 중요하지 않습니다. gc가 없으며을 char[]건드리지 않습니다 (C가 아님).
Tom Hawtin-tackline

필자의 일반적인 시간은 어레이 버전의 경우 ~ 25ms, ArrayList 버전의 경우 ~ 35ms였습니다. 줄의 길이는 15-20 자였습니다. Tom이 말했듯이 문자열 크기는 ~ 100 자 문자열로 타이밍이 거의 동일합니다.
Chris May

3
어떻게 측정 했습니까? Java 마이크로 벤치 마크에서 순진한 측정은 일반적으로 정보보다 더 많은 잘못된 정보를 생성합니다. 위의 진술에주의하십시오.
jmg

6

아니요, 기술적으로 배열은 문자열에 대한 참조 만 저장하기 때문입니다. 문자열 자체는 다른 위치에 할당됩니다. 천 개의 항목의 경우 목록이 더 좋을 것이라고 말하고 싶습니다. 느리지 만 더 많은 유연성을 제공하며 특히 크기를 조정하려는 경우 더 사용하기 쉽습니다.


5
List는 또한 문자열에 대한 참조 만 저장합니다.
Peter Štibraný

6

수천 명이라면 트라이를 사용하는 것이 좋습니다. trie는 저장된 문자열의 공통 접두사를 병합하는 트리와 같은 구조입니다.

예를 들어 문자열이

intern
international
internationalize
internet
internets

트라이는 다음을 저장합니다.

intern
 -> \0
 international
 -> \0
 -> ize\0
 net
 ->\0
 ->s\0

문자열에는 저장을 위해 57 개의 문자 (널 종료 문자 '\ 0'포함)와이를 보유하는 String 객체의 크기가 필요합니다. (사실, 우리는 아마도 모든 크기를 16의 배수로 반올림해야하지만 ...) 대략 57 + 5 = 62 바이트라고 부릅니다.

trie에는 스토리지에 29 (널 종료 문자 '\ 0'포함)와 trie 노드 크기가 필요합니다. 여기에는 배열과 하위 trie 노드 목록이 참조됩니다.

이 예제에서는 아마도 거의 동일 할 것입니다. 수천의 경우 공통 접두사가있는 한 덜 나올 것입니다.

이제 다른 코드에서 trie를 사용할 때 StringBuffer를 중개자로 사용하여 String으로 변환해야합니다. 많은 문자열이 한 번에 문자열로 사용되면 트리 외부에서 손실됩니다.

그러나 사전에 물건을 찾아보기 위해 한 번에 몇 개만 사용하는 경우 trie는 많은 공간을 절약 할 수 있습니다. HashSet에 저장하는 것보다 공간이 훨씬 적습니다.

당신은 당신이 그것들을 "열심히"접근하고 있다고 말합니다. 만약 그것이 알파벳순으로 순차적 인 것을 의미한다면, 트리는 깊이 우선 반복한다면, 무료로 알파벳 순서를 제공합니다.


1
trie는 라이브러리와 같거나 어떻게 만들 수 있습니까?
euphoria83

트라이는 토큰 화 된 문자열의 경우에만 유용하며 누군가가 실행중인 텍스트를 문자열로 저장하는 경우에는 유용하지 않습니다.
MN

5

최신 정보:

Mark가 지적했듯이 JVM 워밍업 (여러 테스트 통과) 후에는 큰 차이가 없습니다. 다시 생성 된 배열 또는 새로운 행의 행렬로 시작하는 새로운 패스로 확인했습니다. 확률이 높으면 인덱스 액세스가있는 간단한 배열을 컬렉션에 사용하지 않아야합니다.

여전히 첫 1-2 패스는 간단한 배열이 2-3 배 빠릅니다.

원래 게시물 :

검사하기에 너무 간단한 주제에 대한 단어가 너무 많습니다. 질문 배열이 없으면 클래스 컨테이너보다 몇 배 빠릅니다 . 나는이 질문에 따라 성능 결정 섹션의 대안을 찾고 있습니다. 실제 상황을 확인하기 위해 작성한 프로토 타입 코드는 다음과 같습니다.

import java.util.List;
import java.util.Arrays;

public class IterationTest {

    private static final long MAX_ITERATIONS = 1000000000;

    public static void main(String [] args) {

        Integer [] array = {1, 5, 3, 5};
        List<Integer> list = Arrays.asList(array);

        long start = System.currentTimeMillis();
        int test_sum = 0;
        for (int i = 0; i < MAX_ITERATIONS; ++i) {
//            for (int e : array) {
            for (int e : list) {
                test_sum += e;
            }
        }
        long stop = System.currentTimeMillis();

        long ms = (stop - start);
        System.out.println("Time: " + ms);
    }
}

그리고 여기에 답이 있습니다 :

어레이 기준 (라인 16이 활성화 됨) :

Time: 7064

목록을 기준으로 (17 행이 활성화 됨) :

Time: 20950

'빠른'에 대한 의견이 더 있습니까? 이것은 매우 이해됩니다. 문제는 List의 유연성보다 약 3 배 더 빠를 때입니다. 그러나 이것은 또 다른 질문입니다. 그건 그렇고 나는 수동으로 생성 한 것을 기반으로 이것을 확인했습니다 ArrayList. 거의 같은 결과입니다.


2
3시간이 더 빠르지 만 사실은 미미합니다. 14ms오랜 시간이 아닙니다
0x6C38

1
벤치 마크는 JVM 예열을 고려하지 않습니다. main ()을 test ()로 변경하고 test를 main에서 반복해서 호출하십시오. 세 번째 또는 네 번째 테스트 실행으로 여러 번 더 빠르게 실행됩니다. 그 시점에서 배열이 배열보다 약 9 배 빠릅니다.
Mike

5

여기에 이미 많은 좋은 대답이 있기 때문에 삽입 및 반복 성능 비교 인 실제보기에 대한 다른 정보를 제공하고 싶습니다 : 기본 배열 대 Java의 링크 된 목록.

이것은 실제로 간단한 성능 점검입니다.
따라서 결과는 기계 성능에 따라 다릅니다.

이에 사용되는 소스 코드는 다음과 같습니다.

import java.util.Iterator;
import java.util.LinkedList;

public class Array_vs_LinkedList {

    private final static int MAX_SIZE = 40000000;

    public static void main(String[] args) {

        LinkedList lList = new LinkedList(); 

        /* insertion performance check */

        long startTime = System.currentTimeMillis();

        for (int i=0; i<MAX_SIZE; i++) {
            lList.add(i);
        }

        long stopTime = System.currentTimeMillis();
        long elapsedTime = stopTime - startTime;
        System.out.println("[Insert]LinkedList insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");

        int[] arr = new int[MAX_SIZE];

        startTime = System.currentTimeMillis();
        for(int i=0; i<MAX_SIZE; i++){
            arr[i] = i; 
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Insert]Array Insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");


        /* iteration performance check */

        startTime = System.currentTimeMillis();

        Iterator itr = lList.iterator();

        while(itr.hasNext()) {
            itr.next();
            // System.out.println("Linked list running : " + itr.next());
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Loop]LinkedList iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");


        startTime = System.currentTimeMillis();

        int t = 0;
        for (int i=0; i < MAX_SIZE; i++) {
            t = arr[i];
            // System.out.println("array running : " + i);
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Loop]Array iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");
    }
}

성능 결과는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오


4

효율성이 필요한 경우 배열을 사용하고 유연성이 필요한 경우 목록을 사용하십시오.


4

ArrayList는 배열을 캡슐화하므로 원시 배열을 사용하는 것과 비교하여 차이가 거의 없습니다 (List가 Java에서 작업하기가 훨씬 쉽다는 점을 제외하고).

배열을 ArrayList보다 선호하는 것이 합리적 인 유일한 시간은 바이트, int 등의 기본 요소를 저장하고 기본 배열을 사용하여 얻는 특정 공간 효율성이 필요한 경우입니다.


4

문자열 객체를 저장하는 경우 배열 대 목록 선택은 그다지 중요하지 않습니다 (성능 고려). 배열과리스트 모두 실제 객체가 아닌 문자열 객체 참조를 저장하기 때문입니다.

  1. 문자열 수가 거의 일정하면 배열 (또는 ArrayList)을 사용하십시오. 그러나 숫자가 너무 다양하면 LinkedList를 사용하는 것이 좋습니다.
  2. 중간에 요소를 추가하거나 삭제해야 할 경우 LinkedList를 사용해야합니다.

4

배열보다 목록을 사용할 때의 성능 영향에 대해 더 나은 느낌을 얻기 위해 여기에 왔습니다. 내 시나리오에 맞게 코드를 수정해야했습니다. 대부분 getter를 사용하여 ~ 1000 int의 배열 / 목록, array [j] 대 list.get (j)

그것에 대해 비 과학적이지 않기 위해 7의 최선을 취하면 (2.5x 느리게 목록이있는 첫 번째 몇 가지) 나는 이것을 얻습니다.

array Integer[] best 643ms iterator
ArrayList<Integer> best 1014ms iterator

array Integer[] best 635ms getter
ArrayList<Integer> best 891ms getter (strange though)

따라서 어레이에서 약 30 % 더 빠릅니다.

게시하는 두 번째 이유는 중첩 루프로 수학 / 매트릭스 / 시뮬레이션 / 최적화 코드를 수행 할 경우 아무도 영향을 언급하지 않기 때문입니다 .

세 개의 중첩 레벨이 있고 내부 루프가 8 배의 성능 히트를보고있는 속도보다 두 배 느리다고 가정하십시오. 하루에 실행될 것이 이제는 일주일이 걸립니다.

* 편집 여기서 충격을주었습니다. 차기 때문에 Integer [1000]보다는 int [1000]을 선언하려고했습니다.

array int[] best 299ms iterator
array int[] best 296ms getter

Integer [] 대 int []를 사용하면 성능이 두 배로 증가합니다. 반복자가있는 ListArray는 int []보다 3 배 느립니다. 실제로 Java의 목록 구현은 기본 배열과 유사하다고 생각했습니다 ...

참조 코드 (여러 번 호출) :

    public static void testArray()
    {
        final long MAX_ITERATIONS = 1000000;
        final int MAX_LENGTH = 1000;

        Random r = new Random();

        //Integer[] array = new Integer[MAX_LENGTH];
        int[] array = new int[MAX_LENGTH];

        List<Integer> list = new ArrayList<Integer>()
        {{
            for (int i = 0; i < MAX_LENGTH; ++i)
            {
                int val = r.nextInt();
                add(val);
                array[i] = val;
            }
        }};

        long start = System.currentTimeMillis();
        int test_sum = 0;
        for (int i = 0; i < MAX_ITERATIONS; ++i)
        {
//          for (int e : array)
//          for (int e : list)          
            for (int j = 0; j < MAX_LENGTH; ++j)
            {
                int e = array[j];
//              int e = list.get(j);
                test_sum += e;
            }
        }

        long stop = System.currentTimeMillis();

        long ms = (stop - start);
        System.out.println("Time: " + ms);
    }

3

데이터의 크기를 미리 알고 있다면 배열이 더 빠릅니다.

목록이 더 유연합니다. 배열이 지원하는 ArrayList를 사용할 수 있습니다.


ArrayList에는 백업 배열을 지정된 크기로 미리 할당하는 ensureCapacity () 메서드가 있습니다.
JesperE 2009

또는 시공 시간에 크기를 지정할 수 있습니다. 또한 여기서 "빠른"은 "하나가 아닌 두 개의 메모리 영역을 할당하는 데 몇 마이크로 초"를 의미합니다
Aaron Digulla

3

고정 된 크기로 살 수 있으며 어레이가 더 빠르며 메모리가 덜 필요합니다.

요소를 추가 및 제거하여 List 인터페이스의 유연성이 필요한 경우 어떤 구현을 선택해야하는지에 대한 질문이 남아 있습니다. 경우에 따라 ArrayList가 권장되고 사용되는 경우가 많지만 목록의 시작 또는 중간에있는 요소를 제거하거나 삽입해야하는 경우 ArrayList의 성능 문제도 있습니다.

따라서 GapList를 소개하는 http://java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-list 를 살펴볼 수 있습니다. 이 새로운 목록 구현은 ArrayList와 LinkedList의 장점을 결합하여 거의 모든 작업에서 매우 우수한 성능을 제공합니다.


2

구현에 따라. 기본 유형의 배열이 ArrayList보다 작고 효율적일 수 있습니다. 이는 배열이 인접한 메모리 블록에 직접 값을 저장하고 가장 간단한 ArrayList 구현은 각 값에 대한 포인터를 저장하기 때문입니다. 특히 64 비트 플랫폼에서 이는 큰 차이를 만들 수 있습니다.

물론, jvm 구현이이 상황에 대해 특별한 경우를 가질 수 있으며,이 경우 성능은 동일합니다.


2

목록은 제네릭을 사용할 수 있으므로 Java 1.5 이상에서 선호되는 방법입니다. 배열에는 제네릭을 사용할 수 없습니다. 또한 배열에는 사전 정의 된 길이가 있으며 동적으로 커질 수 없습니다. 큰 크기의 배열을 초기화하는 것은 좋지 않습니다. ArrayList는 제네릭으로 배열을 선언하는 방법이며 동적으로 커질 수 있습니다. 그러나 삭제 및 삽입이 더 자주 사용되면 연결된 목록이 가장 빠른 데이터 구조입니다.


2

어디에서나 권장되는 배열은 목록 대신 배열을 사용할 수 있습니다. 특히 항목 수와 크기가 변경되지 않는 것을 알고있는 경우에 유용합니다.

Oracle Java 모범 사례 참조 : http://docs.oracle.com/cd/A97688_16/generic.903/bp/java.htm#1007056

물론 컬렉션에서 개체를 여러 번 추가하고 제거해야하는 경우 사용하기 쉬운 목록이 많습니다.


링크 한 문서가 10 년 이상되었으며, 즉 Java 1.3에 적용됩니다. 그 이후로 주요 성능 개선이 이루어졌습니다.
assylias

@assylias는 위의 답변을 볼 수 있으며 성능 테스트가 포함되어 있습니다. 즉, 어레이 속도가 더 빠릅니다.
Nik

3
나는 그들 중 하나를 썼다는 것을 안다. 그러나 " 어레이는 목록 대신 사용할 수있는 모든 곳에서 배열이 권장된다 "고 생각하지 않습니다 . 프리미티브를 다루지 않고 코드가 성능에 민감한 경우가 아니면 대부분의 상황에서 ArrayList가 기본 선택이되어야합니다.
assylias

2

대답 중 어느 것도 내가 관심있는 정보를 가지고 있지 않았습니다. 동일한 배열을 여러 번 반복적으로 스캔했습니다. 이를 위해 JMH 테스트를 작성해야했습니다.

결과 (자바 1.8.0_66 x32, 일반 배열 반복은 ArrayList보다 5 배 이상 빠름) :

Benchmark                    Mode  Cnt   Score   Error  Units
MyBenchmark.testArrayForGet  avgt   10   8.121 ? 0.233  ms/op
MyBenchmark.testListForGet   avgt   10  37.416 ? 0.094  ms/op
MyBenchmark.testListForEach  avgt   10  75.674 ? 1.897  ms/op

테스트

package my.jmh.test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

@State(Scope.Benchmark)
@Fork(1)
@Warmup(iterations = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class MyBenchmark {

    public final static int ARR_SIZE = 100;
    public final static int ITER_COUNT = 100000;

    String arr[] = new String[ARR_SIZE];
    List<String> list = new ArrayList<>(ARR_SIZE);

    public MyBenchmark() {
        for( int i = 0; i < ARR_SIZE; i++ ) {
            list.add(null);
        }
    }

    @Benchmark
    public void testListForEach() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( String str : list ) {
                if( str != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

    @Benchmark
    public void testListForGet() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( int j = 0; j < ARR_SIZE; j++ ) {
                if( list.get(j) != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

    @Benchmark
    public void testArrayForGet() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( int j = 0; j < ARR_SIZE; j++ ) {
                if( arr[j] != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

}

2

"수천"은 많지 않습니다. 수천 개의 단락 길이 문자열은 몇 메가 바이트 크기입니다. 직렬로 액세스 하기 만하면 불변의 단일 링크 List를 사용하십시오 .


대부분의 64 비트 구현에서 8 바이트
Tom Hawtin-tackline

이 것이 java.util.LinkedList보다 빠르다는 증거가 있습니까? '메모리 내'도 어느 것입니까? 마치 차이를 만드는 것처럼 불변으로 만들 수도 있습니다.
Lorne의 후작

1

적절한 벤치마킹없이 최적화의 함정에 빠지지 마십시오. 다른 사람들은 가정하기 전에 프로파일 러 사용을 제안했습니다.

열거 한 다른 데이터 구조는 목적이 다릅니다. 목록은 시작과 끝에 요소를 삽입하는 데 매우 효율적이지만 임의의 요소에 액세스 할 때 많은 어려움을 겪습니다. 어레이는 스토리지가 고정되어 있지만 빠른 임의 액세스를 제공합니다. 마지막으로 ArrayList는 배열이 커지도록하여 인터페이스를 향상시킵니다. 일반적으로 사용할 데이터 구조는 저장된 데이터에 액세스하거나 추가하는 방법에 따라 결정되어야합니다.

메모리 소비에 대하여. 당신은 몇 가지를 혼합하는 것 같습니다. 배열은 보유한 데이터 유형에 대한 지속적인 메모리 청크 만 제공합니다. java는 boolean, char, int, long, float 및 Object라는 고정 된 데이터 유형을 가지고 있음을 잊지 마십시오 (모든 객체를 포함하며 배열도 Object 임). 즉, 문자열 문자열 [1000] 또는 MyObject myObjects [1000] 배열을 선언하면 개체의 위치 (참조 또는 포인터)를 저장할 수있는 1000 개의 메모리 상자 만 가져옵니다. 개체의 크기에 맞게 1000 개의 메모리 상자를 확보 할 수 없습니다. 객체가 "new"로 먼저 생성된다는 것을 잊지 마십시오. 메모리 할당이 완료된 후 나중에 참조 (메모리 주소)가 어레이에 저장됩니다. 객체는 참조로만 배열에 복사되지 않습니다.


1

Strings와 실제로 차이가 있다고 생각하지 않습니다. 문자열 배열에서 연속적인 것은 문자열에 대한 참조이며, 문자열 자체는 메모리의 임의 위치에 저장됩니다.

배열과 목록은 객체가 아닌 기본 유형에 차이를 만들 수 있습니다. 경우 사전에 요소의 수를 알고, 유연성을 필요로하지 않는 사실들이 연속적으로 저장되며 즉시 액세스하기 때문에, 정수 또는 두 배의 수백만의 배열, 메모리 및 변두리 목록보다 속도가 더 효율적입니다. 그렇기 때문에 Java는 여전히 문자열에 문자 배열, 이미지 데이터에 정수 배열 등을 사용합니다.



1

여기에 주어진 많은 마이크로 벤치 마크는 배열 / 배열 목록 읽기와 같은 것들에 대해 몇 나노 초를 발견했습니다. 모든 것이 L1 캐시에 있으면 상당히 합리적입니다.

높은 수준의 캐시 또는 주 메모리 액세스는 L1 캐시의 경우 1nS와 비교하여 10nS-100nS와 같은 크기의 시간을 가질 수 있습니다. ArrayList에 액세스하면 추가 메모리 간접 지정이 있으며 실제 응용 프로그램에서는 액세스 사이에 코드가 수행하는 작업에 따라 거의 모든 비용을 지불 할 수 있습니다. 물론 작은 ArrayList가 많은 경우 메모리 사용량이 증가하고 캐시 누락이 발생할 가능성이 높아집니다.

원래 포스터는 단 하나만 사용하고 짧은 시간에 많은 내용에 액세스하는 것처럼 보이므로 큰 어려움이 아닙니다. 그러나 다른 사람들에게는 다를 수 있으므로 마이크로 벤치 마크를 해석 할 때주의해야합니다.

그러나 Java Strings는 특히 많은 양의 작은 것들을 저장하는 경우 엄청나게 낭비입니다 (메모리 분석기로 살펴보면 문자열이 60 자 이상인 것처럼 보입니다). 문자열 배열에는 String 객체에 대한 간접 성이 있고, String 객체에서 문자열 자체를 포함하는 char []에 대한 다른 것도 있습니다. L1 캐시를 날려 버릴 것이 있다면 수천 또는 수만 개의 문자열과 결합됩니다. 따라서 가능한 한 많은 성능을 폐기하는 것에 대해 진지하게-정말로 진지하게 생각한다면 다르게 수행하는 것을 볼 수 있습니다. 예를 들어, 두 개의 배열, 모든 문자열이 포함 된 char [] 및 시작에 오프셋이있는 int []를 보유 할 수 있습니다. 이것은 무엇이든 할 수있는 PITA가 될 것이므로 거의 필요하지 않습니다. 그리고 그렇게하면


0

액세스 방법에 따라 다릅니다.

저장 후 삽입 / 삭제가 거의 없거나 전혀없는 검색 작업을 주로 수행하려면 배열의 O (1)에서 검색이 수행되는 것처럼 추가 / 삭제는 요소의 순서를 변경해야 할 수도 있습니다. .

저장 후, 검색 작업이 거의 또는 전혀없이 문자열을 추가 / 삭제하는 것이 주된 목적인 경우 목록으로 이동하십시오.


0

ArrayList는 내부적으로 배열 객체를 사용하여 요소를 추가하거나 저장합니다. 즉, ArrayList는 Array data -structure에 의해 지원됩니다. ArrayList의 배열은 크기 조정 가능 (또는 동적)입니다.

ArrayList는 내부적으로 배열을 사용하기 때문에 Array가 Array보다 빠릅니다 . ArrayList에 요소를 직접 추가하고 ArrayList를 통해 Array에 요소를 간접적으로 추가 할 수 있다면 항상 직접 메커니즘이 간접 메커니즘보다 빠릅니다.

ArrayList 클래스에는 두 가지 오버로드 된 add () 메소드가 있습니다.
1. add(Object) : 객체를 목록의 끝에 추가합니다.
2. add(int index , Object ) : 지정된 개체를 목록의 지정된 위치에 삽입합니다.

ArrayList의 크기가 어떻게 동적으로 커 집니까?

public boolean add(E e)        
{       
     ensureCapacity(size+1);
     elementData[size++] = e;         
     return true;
}

위 코드에서 주목할 점은 요소를 추가하기 전에 ArrayList의 용량을 확인하고 있다는 것입니다. ensureCapacity ()는 점유 된 요소의 현재 크기와 배열의 최대 크기를 결정합니다. 채워진 요소의 크기 (ArrayList 클래스에 추가 할 새 요소 포함)가 배열의 최대 크기보다 큰 경우 배열의 크기를 늘리십시오. 그러나 배열의 크기는 동적으로 증가시킬 수 없습니다. 내부적으로 일어나는 일은 새로운 어레이가 용량으로 생성됩니다

자바 6까지

int newCapacity = (oldCapacity * 3)/2 + 1;

(업데이트) Java 7에서

 int newCapacity = oldCapacity + (oldCapacity >> 1);

또한 이전 배열의 데이터가 새 배열로 복사됩니다.

ArrayList에 오버 헤드 메소드가 있으므로 Array가보다 빠릅니다 ArrayList.


0

배열-결과를 더 빨리 가져와야 할 때 항상 더 좋습니다.

목록-O (1)에서 수행 할 수 있으므로 삽입 및 삭제에 대한 결과를 수행하며 데이터를 쉽게 추가, 페치 및 삭제하는 메소드도 제공합니다. 훨씬 사용하기 쉽습니다.

그러나 데이터가 저장된 배열의 인덱스 위치를 알면 항상 데이터 페치가 빠릅니다.

이는 배열을 정렬하여 잘 수행 할 수 있습니다. 따라서 이것은 데이터를 가져 오는 시간을 증가시킵니다 (즉, 데이터 저장 + 데이터 정렬 + 데이터가있는 위치 검색). 따라서 데이터를 더 빨리 가져 오는 데 능숙하더라도 어레이에서 데이터를 가져 오는 데 추가 대기 시간이 늘어납니다.

따라서 이것은 trie 데이터 구조 또는 삼항 데이터 구조로 해결할 수 있습니다. 위에서 논의 된 바와 같이, trie 데이터 구조는 데이터를 검색하는데 매우 효율적일 것이며, 특히 단어에 대한 검색은 O (1) 크기로 수행 될 수있다. 시간이 중요 할 때 즉; 데이터를 빠르게 검색하고 검색해야하는 경우 trie 데이터 구조를 사용할 수 있습니다.

메모리 공간을 적게 소비하고 더 나은 성능을 원한다면 삼항 데이터 구조를 사용하십시오. 둘 다 (예 : 사전에 포함 된 단어와 같이) 많은 수의 문자열을 저장하는 데 적합합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.