Java에서 직렬로 액세스하려면 메모리에 수천 개의 문자열을 유지해야합니다. 배열에 저장해야합니까, 아니면 일종의 List를 사용해야합니까?
배열은 List와 달리 모든 데이터를 인접한 메모리 덩어리에 보관하므로 배열을 사용하여 수천 개의 문자열을 저장하면 문제가 발생합니까?
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(...)
)은 비용이 많이 들고 가능한 경우 초기 용량을 적절한 수준으로 설정해야합니다 (어레이를 사용할 때도 동일한 문제가 발생 함)다음은 표준 x86 데스크톱 시스템에서 JDK 7과 함께 jmh 벤치마킹 라이브러리 (나노초 단위)를 사용하여 세 가지 작업에 대해 측정 한 결과 입니다. 테스트에서 ArrayList의 크기를 조정하여 결과를 비교할 수 없도록하십시오. 벤치 마크 코드는 여기에 있습니다 .
나는 다음 문장을 실행하면서 4 가지 테스트를 실행했습니다.
Integer[] array = new Integer[1];
List<Integer> list = new ArrayList<> (1);
Integer[] array = new Integer[10000];
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 가지 테스트를 실행했다.
return list.get(0);
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 가지 테스트를 실행했다.
list.set(0, value);
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
어떠한 물질 차이 심하고하지 않는다 ).
배열보다 제네릭 형식을 선호해야합니다. 다른 사람들이 언급했듯이 배열은 융통성이 없으며 일반적인 유형의 표현력이 없습니다. (그러나 런타임 유형 검사는 지원하지만 일반 유형과 잘못 혼합됩니다.)
그러나 항상 그렇듯이 최적화 할 때는 항상 다음 단계를 수행해야합니다.
원래 포스터가 C ++ / STL 배경에서 나온 것으로 추측되어 혼란을 겪고 있습니다. C ++에서는 std::list
이중 연결 목록입니다.
Java에서는 [java.util.]List
구현이 필요없는 인터페이스입니다 (C ++ 용어의 순수한 추상 클래스). List
이중 연결 목록이 될 수 있습니다- java.util.LinkedList
제공됩니다. 그러나 새로 만들기를 원할 때 100에서 99 시간이 99 번 대신 C ++과 거의 동일한 List
것을 사용하려고합니다 . 및에 의해 반환되는 것과 같은 다른 표준 구현이 있습니다 .java.util.ArrayList
std::vector
java.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 다시 가서되는 약간 다른 인자의 일반 영어 설명 이 생소한 용어 인 경우.
정렬;
연결된 목록 :
따라서 어레이의 크기를 조정하는 방법에 가장 적합한 것을 선택하십시오. 크기를 조정하고 많이 삽입하고 삭제하면 연결된 목록이 더 나은 선택 일 수 있습니다. 랜덤 액세스가 드물 경우에도 마찬가지입니다. 직렬 액세스에 대해 언급했습니다. 수정이 거의없이 주로 직렬 액세스를 수행하는 경우 선택하는 것이 중요하지 않습니다.
연결 된 목록은 잠재적으로 비 연속적 인 메모리 블록과 다음 요소에 대한 (효과적으로) 포인터를 다루기 때문에 약간 더 높은 오버 헤드가 있습니다. 그러나 수백만 개의 항목을 다루지 않는 한 중요한 요소는 아닙니다.
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();
}
}
}
char[]
건드리지 않습니다 (C가 아님).
아니요, 기술적으로 배열은 문자열에 대한 참조 만 저장하기 때문입니다. 문자열 자체는 다른 위치에 할당됩니다. 천 개의 항목의 경우 목록이 더 좋을 것이라고 말하고 싶습니다. 느리지 만 더 많은 유연성을 제공하며 특히 크기를 조정하려는 경우 더 사용하기 쉽습니다.
수천 명이라면 트라이를 사용하는 것이 좋습니다. 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에 저장하는 것보다 공간이 훨씬 적습니다.
당신은 당신이 그것들을 "열심히"접근하고 있다고 말합니다. 만약 그것이 알파벳순으로 순차적 인 것을 의미한다면, 트리는 깊이 우선 반복한다면, 무료로 알파벳 순서를 제공합니다.
최신 정보:
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
. 거의 같은 결과입니다.
3
시간이 더 빠르지 만 사실은 미미합니다. 14ms
오랜 시간이 아닙니다
여기에 이미 많은 좋은 대답이 있기 때문에 삽입 및 반복 성능 비교 인 실제보기에 대한 다른 정보를 제공하고 싶습니다 : 기본 배열 대 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.");
}
}
성능 결과는 다음과 같습니다.
배열보다 목록을 사용할 때의 성능 영향에 대해 더 나은 느낌을 얻기 위해 여기에 왔습니다. 내 시나리오에 맞게 코드를 수정해야했습니다. 대부분 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);
}
데이터의 크기를 미리 알고 있다면 배열이 더 빠릅니다.
목록이 더 유연합니다. 배열이 지원하는 ArrayList를 사용할 수 있습니다.
고정 된 크기로 살 수 있으며 어레이가 더 빠르며 메모리가 덜 필요합니다.
요소를 추가 및 제거하여 List 인터페이스의 유연성이 필요한 경우 어떤 구현을 선택해야하는지에 대한 질문이 남아 있습니다. 경우에 따라 ArrayList가 권장되고 사용되는 경우가 많지만 목록의 시작 또는 중간에있는 요소를 제거하거나 삽입해야하는 경우 ArrayList의 성능 문제도 있습니다.
따라서 GapList를 소개하는 http://java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-list 를 살펴볼 수 있습니다. 이 새로운 목록 구현은 ArrayList와 LinkedList의 장점을 결합하여 거의 모든 작업에서 매우 우수한 성능을 제공합니다.
어디에서나 권장되는 배열은 목록 대신 배열을 사용할 수 있습니다. 특히 항목 수와 크기가 변경되지 않는 것을 알고있는 경우에 유용합니다.
Oracle Java 모범 사례 참조 : http://docs.oracle.com/cd/A97688_16/generic.903/bp/java.htm#1007056
물론 컬렉션에서 개체를 여러 번 추가하고 제거해야하는 경우 사용하기 쉬운 목록이 많습니다.
대답 중 어느 것도 내가 관심있는 정보를 가지고 있지 않았습니다. 동일한 배열을 여러 번 반복적으로 스캔했습니다. 이를 위해 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);
}
}
"수천"은 많지 않습니다. 수천 개의 단락 길이 문자열은 몇 메가 바이트 크기입니다. 직렬로 액세스 하기 만하면 불변의 단일 링크 List를 사용하십시오 .
적절한 벤치마킹없이 최적화의 함정에 빠지지 마십시오. 다른 사람들은 가정하기 전에 프로파일 러 사용을 제안했습니다.
열거 한 다른 데이터 구조는 목적이 다릅니다. 목록은 시작과 끝에 요소를 삽입하는 데 매우 효율적이지만 임의의 요소에 액세스 할 때 많은 어려움을 겪습니다. 어레이는 스토리지가 고정되어 있지만 빠른 임의 액세스를 제공합니다. 마지막으로 ArrayList는 배열이 커지도록하여 인터페이스를 향상시킵니다. 일반적으로 사용할 데이터 구조는 저장된 데이터에 액세스하거나 추가하는 방법에 따라 결정되어야합니다.
메모리 소비에 대하여. 당신은 몇 가지를 혼합하는 것 같습니다. 배열은 보유한 데이터 유형에 대한 지속적인 메모리 청크 만 제공합니다. java는 boolean, char, int, long, float 및 Object라는 고정 된 데이터 유형을 가지고 있음을 잊지 마십시오 (모든 객체를 포함하며 배열도 Object 임). 즉, 문자열 문자열 [1000] 또는 MyObject myObjects [1000] 배열을 선언하면 개체의 위치 (참조 또는 포인터)를 저장할 수있는 1000 개의 메모리 상자 만 가져옵니다. 개체의 크기에 맞게 1000 개의 메모리 상자를 확보 할 수 없습니다. 객체가 "new"로 먼저 생성된다는 것을 잊지 마십시오. 메모리 할당이 완료된 후 나중에 참조 (메모리 주소)가 어레이에 저장됩니다. 객체는 참조로만 배열에 복사되지 않습니다.
Strings와 실제로 차이가 있다고 생각하지 않습니다. 문자열 배열에서 연속적인 것은 문자열에 대한 참조이며, 문자열 자체는 메모리의 임의 위치에 저장됩니다.
배열과 목록은 객체가 아닌 기본 유형에 차이를 만들 수 있습니다. 경우 사전에 요소의 수를 알고, 유연성을 필요로하지 않는 사실들이 연속적으로 저장되며 즉시 액세스하기 때문에, 정수 또는 두 배의 수백만의 배열, 메모리 및 변두리 목록보다 속도가 더 효율적입니다. 그렇기 때문에 Java는 여전히 문자열에 문자 배열, 이미지 데이터에 정수 배열 등을 사용합니다.
여기에 주어진 많은 마이크로 벤치 마크는 배열 / 배열 목록 읽기와 같은 것들에 대해 몇 나노 초를 발견했습니다. 모든 것이 L1 캐시에 있으면 상당히 합리적입니다.
높은 수준의 캐시 또는 주 메모리 액세스는 L1 캐시의 경우 1nS와 비교하여 10nS-100nS와 같은 크기의 시간을 가질 수 있습니다. ArrayList에 액세스하면 추가 메모리 간접 지정이 있으며 실제 응용 프로그램에서는 액세스 사이에 코드가 수행하는 작업에 따라 거의 모든 비용을 지불 할 수 있습니다. 물론 작은 ArrayList가 많은 경우 메모리 사용량이 증가하고 캐시 누락이 발생할 가능성이 높아집니다.
원래 포스터는 단 하나만 사용하고 짧은 시간에 많은 내용에 액세스하는 것처럼 보이므로 큰 어려움이 아닙니다. 그러나 다른 사람들에게는 다를 수 있으므로 마이크로 벤치 마크를 해석 할 때주의해야합니다.
그러나 Java Strings는 특히 많은 양의 작은 것들을 저장하는 경우 엄청나게 낭비입니다 (메모리 분석기로 살펴보면 문자열이 60 자 이상인 것처럼 보입니다). 문자열 배열에는 String 객체에 대한 간접 성이 있고, String 객체에서 문자열 자체를 포함하는 char []에 대한 다른 것도 있습니다. L1 캐시를 날려 버릴 것이 있다면 수천 또는 수만 개의 문자열과 결합됩니다. 따라서 가능한 한 많은 성능을 폐기하는 것에 대해 진지하게-정말로 진지하게 생각한다면 다르게 수행하는 것을 볼 수 있습니다. 예를 들어, 두 개의 배열, 모든 문자열이 포함 된 char [] 및 시작에 오프셋이있는 int []를 보유 할 수 있습니다. 이것은 무엇이든 할 수있는 PITA가 될 것이므로 거의 필요하지 않습니다. 그리고 그렇게하면
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
.
배열-결과를 더 빨리 가져와야 할 때 항상 더 좋습니다.
목록-O (1)에서 수행 할 수 있으므로 삽입 및 삭제에 대한 결과를 수행하며 데이터를 쉽게 추가, 페치 및 삭제하는 메소드도 제공합니다. 훨씬 사용하기 쉽습니다.
그러나 데이터가 저장된 배열의 인덱스 위치를 알면 항상 데이터 페치가 빠릅니다.
이는 배열을 정렬하여 잘 수행 할 수 있습니다. 따라서 이것은 데이터를 가져 오는 시간을 증가시킵니다 (즉, 데이터 저장 + 데이터 정렬 + 데이터가있는 위치 검색). 따라서 데이터를 더 빨리 가져 오는 데 능숙하더라도 어레이에서 데이터를 가져 오는 데 추가 대기 시간이 늘어납니다.
따라서 이것은 trie 데이터 구조 또는 삼항 데이터 구조로 해결할 수 있습니다. 위에서 논의 된 바와 같이, trie 데이터 구조는 데이터를 검색하는데 매우 효율적일 것이며, 특히 단어에 대한 검색은 O (1) 크기로 수행 될 수있다. 시간이 중요 할 때 즉; 데이터를 빠르게 검색하고 검색해야하는 경우 trie 데이터 구조를 사용할 수 있습니다.
메모리 공간을 적게 소비하고 더 나은 성능을 원한다면 삼항 데이터 구조를 사용하십시오. 둘 다 (예 : 사전에 포함 된 단어와 같이) 많은 수의 문자열을 저장하는 데 적합합니다.