나는 항상 간단하게 사용하는 사람이었습니다.
List<String> names = new ArrayList<>();
인터페이스를 이식성 의 유형 이름으로 사용 하므로 이와 같은 질문을 할 때 코드를 다시 작성할 수 있습니다.
때해야 LinkedList
이상 사용 ArrayList
반대의 경우도 마찬가지?
나는 항상 간단하게 사용하는 사람이었습니다.
List<String> names = new ArrayList<>();
인터페이스를 이식성 의 유형 이름으로 사용 하므로 이와 같은 질문을 할 때 코드를 다시 작성할 수 있습니다.
때해야 LinkedList
이상 사용 ArrayList
반대의 경우도 마찬가지?
답변:
요약 ArrayList
와 함께 ArrayDeque
에서 바람직하다 많은 보다 더 많은 사용 사례 LinkedList
. 확실하지 않은 경우로 시작하십시오 ArrayList
.
LinkedList
및 ArrayList
List 인터페이스의 두 가지 구현입니다. LinkedList
이중 연결 목록으로 구현합니다. ArrayList
동적 크기 조정 배열로 구현합니다.
표준 링크리스트 및 배열 연산과 마찬가지로 다양한 메소드는 서로 다른 알고리즘 런타임을 갖습니다.
에 대한 LinkedList<E>
get(int index)
인 O (N) (와 N / 4 평균과 같이), 그러나 O (1) 시 index = 0
또는 index = list.size() - 1
(이 경우, 또한 사용 getFirst()
하고 getLast()
). 의 주요 장점 중 하나 LinkedList<E>
add(int index, E element)
되고 O (N) (와 N / 4 평균과 같이), 그러나 O (1)이 때, index = 0
또는 index = list.size() - 1
(이 경우, 또한 사용 addFirst()
하고 addLast()
/ add()
). 의 주요 장점 중 하나 LinkedList<E>
remove(int index)
인 O (N) (와 N / 4 평균과 같이), 그러나 O (1) 시 index = 0
또는 index = list.size() - 1
(이 경우, 또한 사용 removeFirst()
하고 removeLast()
). 의 주요 장점 중 하나 LinkedList<E>
Iterator.remove()
인 O (1) . 의 주요 장점 중 하나 LinkedList<E>
ListIterator.add(E element)
인 O (1) . 의 주요 장점 중 하나 LinkedList<E>
참고 : 많은 작업이 필요합니다 에는 평균 n / 4 단계 , 최상의 경우 일정한 수의 단계 (예 : 인덱스 = 0), 최악의 경우 n / 2 단계 (중간 목록)가 필요합니다.
에 대한 ArrayList<E>
get(int index)
이다 O (1) . 주요 장점 ArrayList<E>
add(E element)
인 O (1) 상각 있지만, O (n)이 최악의 배열을 조정하여 복사되어야하므로add(int index, E element)
이다 O (N) (와 N / 2 평균 단계)remove(int index)
이다 O (N) (와 N / 2 평균 단계)Iterator.remove()
이다 O (N) (와 N / 2 평균 단계)ListIterator.add(E element)
이다 O (N) (와 N / 2 평균 단계)참고 : 많은 작업 에는 평균 n / 2 단계 가 필요 합니다. 하며 최상의 경우에는 일정한 수의 단계 (목록 끝), 최악의 경우에는 n 단계 (목록의 시작)가 필요합니다.
LinkedList<E>
반복자를 사용하여 일정한 시간에 삽입하거나 제거 할 수 있지만 요소의 순차적 액세스 만 가능합니다. 즉, 목록을 앞뒤로 걸을 수 있지만 목록에서 위치를 찾는 것은 목록의 크기에 비례하여 시간이 걸립니다. Javadoc에 따르면 "목록에 색인을 생성하는 작업은 목록의 시작 또는 끝에서 더 가까운 쪽을 통과 할 것" 이므로 이러한 방법은 O (n) ( N / 4 , 비록 평균 단계) O (1) 에 대해 index = 0
.
ArrayList<E>
반면에 빠른 임의 읽기 액세스를 허용하므로 일정한 시간에 모든 요소를 가져올 수 있습니다. 그러나 끝을 제외하고 어디에서나 추가하거나 제거하려면 개구부를 만들거나 간격을 채우기 위해 후자의 모든 요소를 이동해야합니다. 또한 기본 배열의 용량보다 많은 요소를 추가하면 새 배열 (1.5 배 크기)이 할당되고 이전 배열이 새 배열에 복사되므로ArrayList
최악의 에는 O (n) 을 더합니다 평균이지만 일정합니다.
따라서 수행하려는 작업에 따라 구현을 선택해야합니다. 두 종류의 List를 반복하는 것은 실질적으로 저렴합니다. ( ArrayList
기술적으로 반복하는 것이 더 빠르지 만 실제로 성능에 민감한 작업을 수행하지 않는 한 걱정하지 않아도됩니다. 두 가지 모두 상수입니다.)
LinkedList
기존 반복기를 재사용 하여 요소를 삽입하고 제거 할 때 발생 하는 주요 이점이 있습니다. 그런 다음 목록을 로컬로만 변경하여 O (1) 에서 이러한 작업을 수행 할 수 있습니다 . 배열 목록에서 나머지 배열을 이동 (즉, 복사)해야합니다. 다른 한편으로, 최악의 경우 O (n) ( n / 2 단계) LinkedList
의 링크를 따르는 수단을 찾는 반면 원하는 위치에서 수학적으로 계산되고 O (1) 에서 액세스 할 수 있습니다 .ArrayList
사용 a의 또 다른 장점 LinkedList
은리스트의 머리에서 추가하거나 제거 할 때 그 작업이기 때문에, 발생하는 O (1) 가있는 동안, O (n)에 대한 ArrayList
. 그 ArrayDeque
대신에 좋은 대안 이 될 수 있습니다LinkedList
헤드에서 추가 및 제거 위한 있지만 이는 아닙니다 List
.
또한 큰 목록이있는 경우 메모리 사용량도 다릅니다. LinkedList
다음 및 이전 요소에 대한 포인터도 저장되므로 a의 각 요소 에는 더 많은 오버 헤드가 있습니다. ArrayLists
이 오버 헤드가 없습니다. 그러나 ArrayLists
요소가 실제로 추가되었는지 여부에 관계없이 용량에 할당 된만큼의 메모리를 차지하십시오.
의 기본 초기 용량 ArrayList
은 매우 작습니다 (Java 1.4-1.8에서 10). 그러나 기본 구현은 배열이므로 많은 요소를 추가하면 배열의 크기를 조정해야합니다. 많은 요소를 추가 할 것임을 알 때 높은 크기 조정 비용을 피하려면 ArrayList
초기 용량이 더 높은 구성 요소를 구성하십시오 .
O(n/2)
또는 같은 것은 없습니다 O(n/4)
. 큰 O 표기법은 연산 이 더 큰 n으로 확장 되는 방법을 알려줍니다 . 그리고 단계를 필요로하는 연산은 단계를 필요로하는 연산과 정확히 동일하게 스케일링 되며, 이는 일정한 요약 또는 요인이 제거되는 이유이다. 그리고 둘 다 입니다. 그리고 그것은 비교 sence되지 아니하므로, 어쨌든 다른 일정 요인이 A를 하나의 다른의를 두 단지 나타낸다 선형 확장 작업을. n/2
n
O(n/2)
O(n/4)
O(n)
LinkedList
ArrayList
O(n/2)
O(n/4)
지금까지 아무도이 목록 LinkedList
보다 더 많은 "많은 것" 이라는 일반적인 합의 외에 이러한 각 목록의 메모리 풋 프린트를 다루지 않은 것으로 보입니다 .ArrayList
보이므로 N null 참조에 대해 두 목록이 얼마나 많이 차지하는지 정확하게 보여주기 위해 숫자 크 런칭을 수행했습니다.
참조는 상대 시스템에서 32 또는 64 비트 (널 (null) 인 경우에도)이므로 32 및 64 비트에 대한 4 개의 데이터 세트를 포함 LinkedLists
했으며ArrayLists
.
참고 :ArrayList
라인에 표시된 크기 는 트리밍 된 목록에 대한 것입니다 . 실제로 백업 어레이의 용량은ArrayList
은 일반적으로 현재 요소 수보다 큽니다.
참고 2 : (BeOnRope 덕분에) JDK6 중반부터 CompressedOops가 기본값으로 기본 설정되었으므로 64 비트 시스템의 경우 아래 값은 기본적으로 32 비트에 해당합니다 (물론 특별히 끄지 않는 한).
결과는 특히 요소 수가 LinkedList
많을 때보 다 훨씬 더 많은 것을 분명히 보여줍니다 ArrayList
. 메모리가 중요한 요소 인 경우을 피하십시오 LinkedLists
.
내가 사용한 공식은 내가 잘못한 것을 알려 주면 고칠 것입니다. 'b'는 32 또는 64 비트 시스템에서 4 또는 8이며 'n'은 요소 수입니다. mod의 이유는 java의 모든 객체가 모두 사용되는지 여부에 관계없이 8 바이트의 배수를 차지하기 때문입니다.
배열 목록 :
ArrayList object header + size integer + modCount integer + array reference + (array oject header + b * n) + MOD(array oject, 8) + MOD(ArrayList object, 8) == 8 + 4 + 4 + b + (12 + b * n) + MOD(12 + b * n, 8) + MOD(8 + 4 + 4 + b + (12 + b * n) + MOD(12 + b * n, 8), 8)
연결 목록 :
LinkedList object header + size integer + modCount integer + reference to header + reference to footer + (node object overhead + reference to previous element + reference to next element + reference to element) * n) + MOD(node object, 8) * n + MOD(LinkedList object, 8) == 8 + 4 + 4 + 2 * b + (8 + 3 * b) * n + MOD(8 + 3 * b, 8) * n + MOD(8 + 4 + 4 + 2 * b + (8 + 3 * b) * n + MOD(8 + 3 * b, 8) * n, 8)
int
4 바이트 또는 8 바이트의 데이터 만 포함하는 객체를 모델링하고 있습니다 . 연결된 목록에는 기본적으로 4 개의 "단어"가 있습니다. 따라서 그래프는 연결된 목록이 배열 목록의 저장을 "5 배"사용한다는 인상을줍니다. 이것은 잘못이다. 오버 헤드는 스케일링 팩터가 아닌 추가 조정으로서 오브젝트 당 16 또는 32 바이트입니다.
CompressedOops
기본값은 모든 최근의 JDK (7, 8, 몇 년 동안 6의 업데이트), 그래서 64 비트의 차이를하지 않습니다에 지금 ArrayList
또는 LinkedList
명시 적으로 위해 압축 죄송를 해제하지 않는 한, 크기를 몇몇 이유.
ArrayList
초기 용량을 지정하지 않고 채울 때 여전히 메모리보다 훨씬 적은 메모리를 사용합니다 LinkedList
.
ArrayList
당신이 원하는 것입니다. LinkedList
거의 항상 (성능) 버그입니다.
왜 LinkedList
짜증나 :
ArrayList
가 사용 된 것보다 느립니다 .ArrayList
어쨌든 상당히 느려질 것입니다.LinkedList
아마도 잘못된 선택 일 수 있기 때문에 소스 에서 보는 것은 좋지 않습니다 .약 10 년 동안 대규모 SOA 웹 서비스에서 운영 성능 엔지니어링을 수행 한 사람으로서 ArrayList보다 LinkedList의 동작을 선호합니다. LinkedList의 정상 상태 처리량은 더 나빠서 더 많은 하드웨어를 구매할 수 있지만 압력이 가해지면 ArrayList의 동작으로 인해 클러스터의 앱이 거의 동 기적으로 배열을 확장하고 큰 배열 크기의 경우 응답 성이 떨어질 수 있습니다. 압력이 가해지는 동안 앱에서 중단이 발생했습니다. 이는 치명적인 동작입니다.
마찬가지로 기본 처리량 tenured 가비지 수집기에서 응용 프로그램의 처리량을 향상시킬 수 있지만 10GB 힙이있는 Java 응용 프로그램을 가져 오면 전체 GC 중에 25 초 동안 응용 프로그램을 잠그면 SOA 응용 프로그램에서 시간 초과 및 실패가 발생할 수 있습니다 너무 자주 발생하면 SLA를 날려 버립니다. CMS 수집기는 더 많은 리소스를 사용하고 동일한 원시 처리량을 달성하지 않더라도 예측 가능하고 대기 시간이 짧기 때문에 훨씬 더 나은 선택입니다.
성능이 의미하는 전부가 처리량이고 대기 시간을 무시할 수있는 경우 ArrayList는 성능을위한 더 나은 선택 일뿐입니다. 직장에서의 경험에서 최악의 대기 시간을 무시할 수 없습니다.
LinkedList
항상 5 배의 메모리를 할당하므로 ArrayList
메모리를 회수하지 않아도 2.5 배의 시간이 필요한 메모리는 여전히 훨씬 적은 메모리를 사용합니다. 큰 배열 할당은 Eden 공간을 우회하기 때문에 실제로 메모리가 충분하지 않으면 GC 동작에 영향을 미치지 않습니다.이 경우 LinkedList
훨씬 더 일찍 터졌습니다.
LinkedList
다음 요소에 할당하려면 사용 가능한 작은 메모리 조각 만 필요합니다. 크기 조정 된 배열을 할당하려면 ArrayList
크고 연속적인 여유 공간 블록 이 필요합니다 . 힙이 조각화되면 GC는 적절한 단일 메모리 블록을 확보하기 위해 전체 힙을 다시 정렬 할 수 있습니다.
Algorithm ArrayList LinkedList
seek front O(1) O(1)
seek back O(1) O(1)
seek to index O(1) O(N)
insert at front O(N) O(1)
insert at back O(1) O(1)
insert after an item O(N) O(1)
ArrayLists는 한 번만 읽을 수있는 많은 또는 어 펜더에는 좋지만 추가 또는 제거는 앞면 또는 가운데에는 좋지 않습니다.
O(1)
. 삽입 점을 찾으려면 목록의 절반을 실행해야합니다.
LinkedList
이다 O(1)
당신이 삽입 위치에 반복자가있는 경우 , 즉 ListIterator.add
가정입니다 O(1)
A에 대한 LinkedList
.
그래, 나는 이것이 고대의 질문이라는 것을 알고 있지만 내 두 센트를 던져 넣을 것이다.
LinkedList는 성능 측면에서 거의 항상 잘못된 선택입니다. LinkedList가 필요한 매우 구체적인 알고리즘이 있지만 매우 드물며, 일반적으로이 알고리즘은 일반적으로 LinkedList의 목록 중간에 요소를 빠르게 삽입하고 삭제하는 기능에 의존합니다. ListIterator와 함께.
LinkedList가 ArrayList를 능가하는 일반적인 사용 사례가 하나 있습니다. 그러나 목표가 성능 인 경우 LinkedList 대신 ArrayBlockingQueue (대기열 큐 크기의 상한을 미리 결정하고 모든 메모리를 미리 할당 할 수있는 경우) 또는이 CircularArrayList 구현을 사용하는 것도 고려해야 합니다. . (예, 2001 년부터 생성되었으므로 생성해야하지만 최근 JVM의 기사에서 인용 한 것과 비슷한 성능 비율을 얻었습니다)
ArrayDeque
. docs.oracle.com/javase/6/docs/api/java/util/ArrayDeque.html
ArrayDeque
LinkedList
모든 작업이 같은 끝에 있지 않으면 보다 느립니다 . 스택으로 사용하면 괜찮지 만 대기열을 잘 만들지는 않습니다.
ArrayDeque
Stack
스택으로 LinkedList
사용될 때보 다 더 빠르며 대기열로 사용될 때보 다 빠를 가능성이 높습니다 .
ArrayDeque
문서 에서 인용 한 것 입니다.
효율성 문제입니다. LinkedList
요소를 추가하고 삭제하는 데 빠르지 만 특정 요소에 액세스하는 데 느립니다. ArrayList
특정 요소에 액세스하는 데 빠르지 만 양쪽 끝에 추가하는 것이 느릴 수 있으며 특히 중간에 삭제하는 것이 느릴 수 있습니다.
Linked List 와 마찬가지로 Array vs ArrayList vs LinkedList vs Vector 는 더 깊이 있습니다.
정확하거나 잘못됨 : 테스트를 로컬에서 실행하고 스스로 결정하십시오!
편집 / 제거 빠른에 LinkedList
비해 ArrayList
.
ArrayList
Array
크기가 두 배가되어야하는에 의해 지원되는 대량 응용 프로그램에서는 더 나쁩니다.
아래는 각 작업에 대한 단위 테스트 결과입니다. 타이밍은 나노초 단위로 제공됩니다.
Operation ArrayList LinkedList
AddAll (Insert) 101,16719 2623,29291
Add (Insert-Sequentially) 152,46840 966,62216
Add (insert-randomly) 36527 29193
remove (Delete) 20,56,9095 20,45,4904
contains (Search) 186,15,704 189,64,981
코드는 다음과 같습니다.
import org.junit.Assert;
import org.junit.Test;
import java.util.*;
public class ArrayListVsLinkedList {
private static final int MAX = 500000;
String[] strings = maxArray();
////////////// ADD ALL ////////////////////////////////////////
@Test
public void arrayListAddAll() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
watch.start();
arrayList.addAll(stringList);
watch.totalTime("Array List addAll() = ");//101,16719 Nanoseconds
}
@Test
public void linkedListAddAll() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
watch.start();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(stringList);
watch.totalTime("Linked List addAll() = "); //2623,29291 Nanoseconds
}
//Note: ArrayList is 26 time faster here than LinkedList for addAll()
///////////////// INSERT /////////////////////////////////////////////
@Test
public void arrayListAdd() {
Watch watch = new Watch();
List<String> arrayList = new ArrayList<String>(MAX);
watch.start();
for (String string : strings)
arrayList.add(string);
watch.totalTime("Array List add() = ");//152,46840 Nanoseconds
}
@Test
public void linkedListAdd() {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
watch.start();
for (String string : strings)
linkedList.add(string);
watch.totalTime("Linked List add() = "); //966,62216 Nanoseconds
}
//Note: ArrayList is 9 times faster than LinkedList for add sequentially
/////////////////// INSERT IN BETWEEN ///////////////////////////////////////
@Test
public void arrayListInsertOne() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX + MAX / 10);
arrayList.addAll(stringList);
String insertString0 = getString(true, MAX / 2 + 10);
String insertString1 = getString(true, MAX / 2 + 20);
String insertString2 = getString(true, MAX / 2 + 30);
String insertString3 = getString(true, MAX / 2 + 40);
watch.start();
arrayList.add(insertString0);
arrayList.add(insertString1);
arrayList.add(insertString2);
arrayList.add(insertString3);
watch.totalTime("Array List add() = ");//36527
}
@Test
public void linkedListInsertOne() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(stringList);
String insertString0 = getString(true, MAX / 2 + 10);
String insertString1 = getString(true, MAX / 2 + 20);
String insertString2 = getString(true, MAX / 2 + 30);
String insertString3 = getString(true, MAX / 2 + 40);
watch.start();
linkedList.add(insertString0);
linkedList.add(insertString1);
linkedList.add(insertString2);
linkedList.add(insertString3);
watch.totalTime("Linked List add = ");//29193
}
//Note: LinkedList is 3000 nanosecond faster than ArrayList for insert randomly.
////////////////// DELETE //////////////////////////////////////////////////////
@Test
public void arrayListRemove() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
arrayList.addAll(stringList);
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
arrayList.remove(searchString0);
arrayList.remove(searchString1);
watch.totalTime("Array List remove() = ");//20,56,9095 Nanoseconds
}
@Test
public void linkedListRemove() throws Exception {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(Arrays.asList(strings));
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
linkedList.remove(searchString0);
linkedList.remove(searchString1);
watch.totalTime("Linked List remove = ");//20,45,4904 Nanoseconds
}
//Note: LinkedList is 10 millisecond faster than ArrayList while removing item.
///////////////////// SEARCH ///////////////////////////////////////////
@Test
public void arrayListSearch() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
arrayList.addAll(stringList);
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
arrayList.contains(searchString0);
arrayList.contains(searchString1);
watch.totalTime("Array List addAll() time = ");//186,15,704
}
@Test
public void linkedListSearch() throws Exception {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(Arrays.asList(strings));
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
linkedList.contains(searchString0);
linkedList.contains(searchString1);
watch.totalTime("Linked List addAll() time = ");//189,64,981
}
//Note: Linked List is 500 Milliseconds faster than ArrayList
class Watch {
private long startTime;
private long endTime;
public void start() {
startTime = System.nanoTime();
}
private void stop() {
endTime = System.nanoTime();
}
public void totalTime(String s) {
stop();
System.out.println(s + (endTime - startTime));
}
}
private String[] maxArray() {
String[] strings = new String[MAX];
Boolean result = Boolean.TRUE;
for (int i = 0; i < MAX; i++) {
strings[i] = getString(result, i);
result = !result;
}
return strings;
}
private String getString(Boolean result, int i) {
return String.valueOf(result) + i + String.valueOf(!result);
}
}
LinkedList
모든 요소에 대해 5 개의 필드가있는 노드 오브젝트가 있으므로 훨씬 더 많은 메모리 오버 헤드가 있습니다. 많은 시스템에서 20 바이트의 오버 헤드가 발생합니다. 요소 당 평균 메모리 오버 헤드 ArrayList
는 1.5 워드이므로 최악의 경우 6 바이트, 8 바이트가됩니다.
removeIf(element -> condition)
적절한 위치에 사용할 수 있습니다 ArrayList
. 모든 개별 요소에 대해 전체 나머지를 이동할 필요가 없으므로 반복자를 통해 제거 하는 것보다 훨씬 빠릅니다 . 이론 상으로는 O (1) LinkedList
과 같이 이것이 특정 시나리오에 따라 성능이 더 나빠지 LinkedList
지만 단일 노드 만 제거하려면 여러 개의 메모리 액세스가 필요하며, 이는 ArrayList
많은 수의 요소를 제거 할 때 필요한 수를 쉽게 초과 할 수 있습니다. .
ArrayList
본질적으로 배열입니다. LinkedList
이중 연결 목록으로 구현됩니다.
은 get
매우 분명하다. 인덱스를 사용하여 임의 액세스를 허용 ArrayList
하므로 O (1) for ArrayList
입니다. LinkedList
인덱스를 먼저 찾아야하므로 O (n) for 입니다. 참고 : add
및의 버전이 다릅니다 remove
.
LinkedList
추가 및 제거 속도가 빠르지 만 가져 오기 속도가 느립니다. 간단히 말해 다음과 같은 LinkedList
경우에 선호됩니다.
=== ArrayList ===
=== LinkedList의 ===
추가 (E e)
추가 (int index, E element)
여기에서 도면이다 programcreek.com는 ( add
및 remove
제 1 타입은, 즉,리스트의 마지막 요소를 추가하고 목록에서 지정된 위치에있는 요소를 삭제한다.)
LinkedList의 저자 Joshua Bloch :
누구든지 실제로 LinkedList를 사용합니까? 나는 그것을 썼고 결코 사용하지 않았다.
링크 : https://twitter.com/joshbloch/status/583813919019573248
다른 답변만큼 유익하지 않은 답변에 대해 유감스럽게 생각하지만 가장 흥미롭고 설명이 필요하다고 생각했습니다.
ArrayList
무작위로 액세스 할 수 있지만 LinkedList
요소를 확장하고 제거 하는 것이 실제로 저렴합니다. 대부분의 경우 ArrayList
괜찮습니다.
큰 목록을 작성하고 병목 현상을 측정하지 않으면 차이에 대해 걱정할 필요가 없습니다.
TL; DR 은 최신 컴퓨터 아키텍처로 인해 ArrayList
거의 모든 사용 사례에서 훨씬 더 효율적이므로 LinkedList
매우 독특하고 극단적 인 경우를 제외하고는 피해야합니다.
이론적으로 LinkedList는 O (1) add(E element)
또한 목록 중간에 요소를 추가하는 것이 매우 효율적입니다.
LinkedList는 캐시 적대적 데이터 구조 이므로 실습은 매우 다릅니다 . 성능 POV LinkedList
에서 캐시 친화적 인 것보다 성능이 더 좋은 경우는 거의 없습니다 ArrayList
.
다음은 임의의 위치에 요소를 삽입하는 벤치 마크 테스트 결과입니다. 보다시피, 배열 목록은 훨씬 더 효율적이지만 이론적으로 목록 중간에있는 각 삽입물 은 배열 의 n 개 이후 요소를 "이동"해야합니다 (낮은 값이 더 낫습니다).
차세대 하드웨어 (더 크고 효율적인 캐시)에서 작업-결과는 더욱 결정적입니다.
LinkedList는 동일한 작업을 수행하는 데 훨씬 더 많은 시간이 걸립니다. 출처 소스 코드
이에 대한 두 가지 주요 이유가 있습니다.
주로 -노드가 LinkedList
메모리에 무작위로 흩어져 있습니다. RAM ( "Random Access Memory")은 실제로 무작위가 아니며 캐시하기 위해 메모리 블록을 가져와야합니다. 이 작업에는 시간이 걸리고 이러한 페치가 자주 발생하면 캐시의 메모리 페이지를 항상 교체해야합니다.-> 캐시 누락-> 캐시가 효율적이지 않습니다.
ArrayList
요소는 연속 메모리에 저장됩니다. 이는 현대 CPU 아키텍처가 최적화하는 것과 정확히 같습니다.
보조 LinkedList
포인터가 뒤로 / 앞으로 포인터를 유지하는 데 필요합니다 ArrayList
.
btw 인 DynamicIntArray 는 사용자 정의 ArrayList 구현입니다.Int
객체가 아닌 (primitive type)을 이므로 모든 데이터가 실제로 인접하게 저장되므로 훨씬 더 효율적입니다.
기억해야 할 핵심 요소는 메모리 블록 페치 비용이 단일 메모리 셀 액세스 비용보다 중요하다는 것입니다. 따라서 1MB의 순차 메모리 리더가 다른 메모리 블록에서이 양의 데이터를 읽는 것보다 최대 x400 배 더 빠릅니다.
Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns 14x L1 cache
Mutex lock/unlock 25 ns
Main memory reference 100 ns 20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy 3,000 ns 3 us
Send 1K bytes over 1 Gbps network 10,000 ns 10 us
Read 4K randomly from SSD* 150,000 ns 150 us ~1GB/sec SSD
Read 1 MB sequentially from memory 250,000 ns 250 us
Round trip within same datacenter 500,000 ns 500 us
Read 1 MB sequentially from SSD* 1,000,000 ns 1,000 us 1 ms ~1GB/sec SSD, 4X memory
Disk seek 10,000,000 ns 10,000 us 10 ms 20x datacenter roundtrip
Read 1 MB sequentially from disk 20,000,000 ns 20,000 us 20 ms 80x memory, 20X SSD
Send packet CA->Netherlands->CA 150,000,000 ns 150,000 us 150 ms
요점을 더 명확하게하기 위해 목록의 시작 부분에 요소를 추가하는 기준을 확인하십시오. 이론 상으로는 LinkedList
실제로 빛을 발 ArrayList
해야하며 열악한 또는 더 나쁜 경우 결과를 제시 해야하는 유스 케이스입니다.
참고 : 이것은 C ++ Std lib의 벤치 마크이지만 이전 경험에서는 C ++ 및 Java 결과가 매우 유사하다는 것을 보여주었습니다. 소스 코드
다시, 이론을 변경하고 실제로 만들기 - 메모리의 연속 대량 복사하는 것은 현대의 CPU에 의해 최적화 된 작업입니다 ArrayList
/ Vector
훨씬 더 효율적
크레딧 : 여기에 게시 된 모든 벤치 마크는 Kjell Hedström에 의해 작성되었습니다 . 그의 블로그 에서 더 많은 데이터를 찾을 수 있습니다
코드가있는 경우 add(0)
와 remove(0)
하는을 사용 LinkedList
하며 예뻐입니다 addFirst()
및 removeFirst()
방법. 그렇지 않으면을 사용하십시오 ArrayList
.
물론 구아바 의 ImmutableList 는 가장 친한 친구입니다.
다음은 모두 빅 - 오 표기법 ArrayList
과 LinkedList
도는CopyOnWrite-ArrayList
:
배열 목록
get O(1)
add O(1)
contains O(n)
next O(1)
remove O(n)
iterator.remove O(n)
연결 목록
get O(n)
add O(1)
contains O(n)
next O(1)
remove O(1)
iterator.remove O(1)
CopyOnWrite-ArrayList
get O(1)
add O(n)
contains O(n)
next O(1)
remove O(n)
iterator.remove O(n)
이를 바탕으로 무엇을 선택해야하는지 결정해야합니다. :)
LinkedList.add()
여기에있는 대부분의 답변이 그렇게 말하지만, 둘 다 아닙니다 .
매개 변수 아래에서 LinkedList와 ArrayList wrt를 비교해 보겠습니다.
ArrayList 는 목록 인터페이스의 크기 조정이 가능한 배열 구현입니다.
LinkedList 는 목록 인터페이스의 이중 연결 목록 구현입니다.
ArrayList get (int index) 연산은 상수 시간, 즉 O (1)에서 실행되는 동안
LinkedList get (int index) 작업 런타임은 O (n)입니다.
ArrayList 가 LinkedList보다 빠른 이유는 ArrayList 가 내부적으로 배열 데이터 구조를 사용하기 때문에 요소에 인덱스 기반 시스템을 사용하기 때문입니다.
LinkedList 는 지정된 요소 인덱스에서 노드를 검색하기 위해 시작 또는 끝 (둘 중 더 가까운 쪽)을 반복하므로 요소에 대한 인덱스 기반 액세스를 제공하지 않습니다.
LinkedList의 삽입 은 일반적으로 ArrayList에 비해 빠릅니다. LinkedList에서 추가 또는 삽입은 O (1) 연산입니다.
ArrayList에 있는 동안 배열이 전체 최악의 경우 배열 크기를 조정하고 새 배열에 요소를 복사하는 추가 비용이 발생하여 ArrayList O (n)에서 추가 작업을 런타임으로 수행합니다. 그렇지 않으면 O (1)입니다 .
LinkedList의 제거 작업은 일반적으로 ArrayList와 동일합니다 (예 : O (n)).
에서 LinkedList의 두 오버로드 제거 방법이 있습니다. 하나는 목록의 헤드를 제거하고 상수 시간 O (1)에서 실행되는 매개 변수가없는 remove ()입니다. LinkedList의 다른 오버로드 된 remove 메소드는 remove (int) 또는 remove (Object)로, Object 또는 int를 매개 변수로 제거합니다. 이 메소드는 오브젝트를 찾을 때까지 LinkedList를 순회하고 원래 목록에서 링크를 해제합니다. 따라서이 메소드 런타임은 O (n)입니다.
반면에 ArrayList를 제거 (int)에있어서, 갱신 된 새로운 배열 이전 배열 요소를 복사하는 것을 포함 따라서 그 런타임은 O (N)이다.
DownedIterator ()를 사용하여 LinkedList 를 역방향으로 반복 할 수 있습니다.
ArrayList 에는 내려가는 Iterator ()가 없으므로 ArrayList를 거꾸로 반복하려면 자체 코드를 작성해야합니다.
생성자가 오버로드되지 않으면 ArrayList 는 초기 용량 10의 빈 목록을 작성하지만
LinkedList 는 초기 용량없이 빈 목록 만 구성합니다.
LinkedList 의 노드는 다음 및 이전 노드의 주소를 유지해야하므로 LinkedList의 메모리 오버 헤드는 ArrayList에 비해 더 높습니다. 동안
에서는 ArrayList를 각 인덱스 만 실제 객체 (데이터)를 보유하고있다.
나는 일반적으로 특정 목록에서 수행 할 작업의 시간 복잡성에 따라 다른 것을 사용합니다.
|---------------------|---------------------|--------------------|------------|
| Operation | ArrayList | LinkedList | Winner |
|---------------------|---------------------|--------------------|------------|
| get(index) | O(1) | O(n) | ArrayList |
| | | n/4 steps in avg | |
|---------------------|---------------------|--------------------|------------|
| add(E) | O(1) | O(1) | LinkedList |
| |---------------------|--------------------| |
| | O(n) in worst case | | |
|---------------------|---------------------|--------------------|------------|
| add(index, E) | O(n) | O(n) | LinkedList |
| | n/2 steps | n/4 steps | |
| |---------------------|--------------------| |
| | | O(1) if index = 0 | |
|---------------------|---------------------|--------------------|------------|
| remove(index, E) | O(n) | O(n) | LinkedList |
| |---------------------|--------------------| |
| | n/2 steps | n/4 steps | |
|---------------------|---------------------|--------------------|------------|
| Iterator.remove() | O(n) | O(1) | LinkedList |
| ListIterator.add() | | | |
|---------------------|---------------------|--------------------|------------|
|--------------------------------------|-----------------------------------|
| ArrayList | LinkedList |
|--------------------------------------|-----------------------------------|
| Allows fast read access | Retrieving element takes O(n) |
|--------------------------------------|-----------------------------------|
| Adding an element require shifting | o(1) [but traversing takes time] |
| all the later elements | |
|--------------------------------------|-----------------------------------|
| To add more elements than capacity |
| new array need to be allocated |
|--------------------------------------|
배열 목록은 본질적으로 항목 등을 추가하는 메소드가있는 배열입니다 (대신 일반 목록을 사용해야합니다). 인덱서를 통해 액세스 할 수있는 항목의 모음입니다 (예 : [0]). 한 항목에서 다음 항목으로의 진행을 의미합니다.
연결된 목록은 한 항목에서 다음 항목으로의 진행을 지정합니다 (항목 a-> 항목 b). 배열 목록과 동일한 효과를 얻을 수 있지만 연결된 목록은 이전 항목을 따라야 할 항목을 절대적으로 나타냅니다.
Java Tutorials-List 구현을 참조하십시오 .
링크 된 목록의 중요한 기능 (다른 답변에서 읽지 않은)은 두 목록의 연결입니다. 배열을 사용하면 연결된 목록이있는 O (n) (+ 일부 재 할당의 오버 헤드)이며 이것은 O (1) 또는 O (2) ;-)입니다.
중요 : Java의 경우 LinkedList
이는 사실이 아닙니다! Java에 링크 된 목록에 빠른 연결 방법이 있습니까?를 참조하십시오 .
next
한 목록에서 두 번째 목록의 첫 번째 노드를 가리킬 수는 없습니다 . 유일한 방법은 addAll()
요소를 순차적으로 추가하는 것입니다 add()
. 각 요소를 반복하고 호출하는 것보다 낫습니다 . O (1)에서이 작업을 빠르게 수행하려면 org.apache.commons.collections.collection.CompositeCollection과 같은 합성 클래스가 필요하지만 모든 종류의 List / Collection에서 작동합니다.
ArrayList 및 LinkedList에는 고유 한 장단점이 있습니다.
ArrayList는 다음 노드에 대한 포인터를 사용하는 LinkedList와 비교하여 연속 메모리 주소를 사용합니다. 따라서 ArrayList에서 요소를 찾으려면 LinkedList로 n 반복을 수행하는 것보다 빠릅니다.
반면에, LinkedList에서의 삽입과 삭제는 포인터를 변경하기 만하면되기 때문에 훨씬 쉽습니다. ArrayList는 삽입이나 삭제를 위해 shift 연산을 사용합니다.
앱에서 자주 검색 작업을 수행하는 경우 ArrayList를 사용하십시오. 자주 삽입하고 삭제하는 경우 LinkedList를 사용하십시오.
응답을 읽었지만 의견을 듣기 위해 공유하려는 ArrayList에 대해 항상 LinkedList를 사용하는 시나리오가 있습니다.
DB에서 얻은 데이터 목록을 반환하는 메서드를 사용할 때마다 항상 LinkedList를 사용합니다.
내 근거는 내가 얻는 결과의 수를 정확히 알 수 없기 때문에 (메모리 용량과 실제 요소 수의 차이가있는 ArrayList에서와 같이) 메모리 낭비가 없으며, 시도하는 데 시간이 낭비되지 않는다는 것입니다 용량을 복제하십시오.
ArrayList까지는 배열의 중복을 최소화하기 위해 항상 초기 용량의 생성자를 사용해야한다는 데 동의합니다.
ArrayList
그리고 LinkedList
모두 구현 List interface
및 그 방법과 결과는 거의 동일하다. 그러나 요구 사항에 따라 다른 것보다 더 나은 차이점이 거의 없습니다.
1) Search:
ArrayList
검색 작업은 검색 작업에 비해 매우 빠릅니다 LinkedList
. get(int index)
의는 ArrayList
의 성능을 제공 O(1)
하면서 LinkedList
성능입니다 O(n)
.
Reason:
ArrayList
배열 데이터 구조를 암시 적으로 사용하므로 목록에서 요소를 더 빠르게 검색 할 수 있으므로 요소에 대한 색인 기반 시스템을 유지 관리합니다. 다른쪽에 LinkedList
는 요소를 검색하기 위해 모든 요소를 통과해야하는 이중 연결 목록이 구현되어 있습니다.
2) Deletion:
LinkedList
제거 작업은 O(1)
성능을 ArrayList
제공 하는 동시에 가변 성능을 제공합니다. O(n)
최악의 경우 (첫 번째 요소를 제거하는 동안) 및 O(1)
최상의 경우 (마지막 요소를 제거하는 동안).
결론 : LinkedList 요소 삭제는 ArrayList에 비해 빠릅니다.
이유 : LinkedList의 각 요소는 목록의 두 인접 요소를 가리키는 두 개의 포인터 (주소)를 유지합니다. 따라서 제거하려면 제거 할 노드의 두 인접 노드 (요소)에서 포인터 위치 만 변경하면됩니다. ArrayList에있는 동안 모든 요소는 제거 된 요소로 생성 된 공간을 채우기 위해 이동해야합니다.
3) Inserts Performance:
LinkedList
add 메소드는 최악의 경우 O(1)
성능을 ArrayList
제공합니다 O(n)
. 이유는 제거에 대해 설명 된 것과 같습니다.
4) Memory Overhead:
ArrayList
인덱스 및 요소 데이터를 LinkedList
유지하면서 요소 데이터와 인접 노드에 대한 두 개의 포인터를 유지
따라서 LinkedList에서 메모리 소비가 비교적 높습니다.
iterator
및 listIterator
이러한 클래스에 의해 반환은 fail-fast
(리스트가 구조적으로 반복자의 작성 후에 이외 방법으로 변경되면 iterator’s
자신의 제거 또는 추가 방법, 반복자 것이다 ).throw
ConcurrentModificationException
(O(1))
에 LinkedList
비해 우수한 성능 을 제공 합니다 ArrayList(O(n))
.
따라서 응용 프로그램에 빈번한 추가 및 삭제가 필요한 경우 LinkedList가 최선의 선택입니다.
get method
) 작업은 빠르지 Arraylist (O(1))
만 빠르지 않습니다LinkedList (O(n))
따라서 추가 및 제거 작업이 적고 검색 작업 요구 사항이 더 많으면 ArrayList가 가장 좋습니다.
1) 기본 데이터 구조
ArrayList와 LinkedList의 첫 번째 차이점은 ArrayList는 Array에 의해 지원되고 LinkedList는 LinkedList에 의해 지원된다는 사실입니다. 이로 인해 성능에 차이가 생길 수 있습니다.
2) LinkedList는 Deque를 구현합니다.
ArrayList와 LinkedList의 또 다른 차이점은 LinkedList는 List 인터페이스와 별도로 add () 및 poll () 및 기타 여러 Deque 함수에 대한 첫 번째 작업을 제공하는 Deque 인터페이스도 구현한다는 것입니다. 3) ArrayList에 요소 추가 ArrayList에 요소를 추가하는 것은 Array의 크기 조정을 트리거하지 않으면 O (1) 연산입니다.이 경우 O (log (n))가됩니다. 반면에 LinkedList는 탐색이 필요하지 않으므로 O (1) 조작입니다.
4) 위치에서 요소 제거
특정 인덱스에서 요소를 제거하기 위해 (예 : remove (index)를 호출하여) ArrayList는 복사 작업을 수행하여 O (n)에 가깝게 만드는 반면 LinkedList는 해당 지점으로 이동하여 O (n / 2)로 만듭니다. 근접성에 따라 어느 방향에서나 이동할 수 있기 때문입니다.
5) ArrayList 또는 LinkedList를 반복
반복은 LinkedList 및 ArrayList 둘 다에 대한 O (n) 연산입니다. 여기서 n은 요소의 수입니다.
6) 위치에서 요소 검색
get (index) 연산은 ArrayList에서 O (1)이고 LinkedList에서 O (n / 2)는 해당 항목까지 통과해야하기 때문에 필요합니다. 그러나 큰 O 표기법에서 O (n / 2)는 상수를 무시하기 때문에 O (n)입니다.
7) 기억
LinkedList는 랩퍼 오브젝트 인 Entry를 사용하여 데이터를 저장하기위한 정적 중첩 클래스이며 다음과 이전의 두 노드를 저장하는 반면 ArrayList는 Array에 데이터를 저장합니다.
따라서 ArrayList의 경우 메모리가 하나의 Array에서 다른 Array로 내용을 복사 할 때 크기 조정 작업을 수행하는 경우를 제외하고 LinkedList보다 메모리 요구 사항이 적습니다.
배열이 충분히 크면 해당 시점에서 많은 메모리가 소비되고 가비지 수집이 트리거되어 응답 시간이 느려질 수 있습니다.
ArrayList와 LinkedList의 위의 모든 차이점에서 remove () 또는 get ()보다 add () 작업을 자주 수행하는 경우를 제외하고 거의 모든 경우 ArrayList가 LinkedList보다 더 나은 선택 인 것 같습니다.
연결된 목록이 내부적으로 해당 위치에 대한 참조를 유지하고 O (1) 시간에 액세스 할 수 있기 때문에 시작 또는 종료에서 요소를 추가하거나 제거하는 경우 ArrayList보다 연결 목록을 수정하는 것이 더 쉽습니다.
즉, 요소를 추가하려는 위치에 도달하기 위해 링크 된 목록을 탐색 할 필요가 없습니다.이 경우 추가는 O (n) 연산이됩니다. 예를 들어, 링크 된 목록의 중간에 요소를 삽입하거나 삭제합니다.
제 생각에는 Java의 실질적인 목적을 위해 LinkedList보다 ArrayList를 사용하십시오.
여기서 본 테스트 중 하나는 테스트를 한 번만 수행합니다. 그러나 내가 주목 한 것은 이러한 테스트를 여러 번 실행해야하며 결국 시간이 수렴한다는 것입니다. 기본적으로 JVM은 예열해야합니다. 내 특정 유스 케이스의 경우 약 500 개의 항목으로 확장되는 목록에 항목을 추가 / 제거해야했습니다. 내 테스트에서 LinkedList
와 빠른 나온 LinkedList
약 50,000 NS에오고 ArrayList
제공하거나 걸릴 ...에서 90,000 주위 NS오고. 아래 코드를 참조하십시오.
public static void main(String[] args) {
List<Long> times = new ArrayList<>();
for (int i = 0; i < 100; i++) {
times.add(doIt());
}
System.out.println("avg = " + (times.stream().mapToLong(x -> x).average()));
}
static long doIt() {
long start = System.nanoTime();
List<Object> list = new LinkedList<>();
//uncomment line below to test with ArrayList
//list = new ArrayList<>();
for (int i = 0; i < 500; i++) {
list.add(i);
}
Iterator it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
long end = System.nanoTime();
long diff = end - start;
//uncomment to see the JVM warmup and get faster for the first few iterations
//System.out.println(diff)
return diff;
}
remove ()와 insert ()는 모두 ArrayList와 LinkedList에 대해 런타임 효율성이 O (n)입니다. 그러나 선형 처리 시간의 배후에있는 이유는 두 가지 매우 다른 이유 때문입니다.
ArrayList에서는 O (1)의 요소에 도달하지만 실제로 다음 항목을 모두 변경해야하기 때문에 무언가를 제거하거나 삽입하면 O (n)이됩니다.
LinkedList에서 원하는 요소에 실제로 도달하려면 O (n)이 필요합니다. 원하는 인덱스에 도달 할 때까지 맨 처음부터 시작해야하기 때문입니다. remove ()에 대한 1 개의 참조와 insert ()에 대한 2 개의 참조 만 변경하면되기 때문에 실제로 제거하거나 삽입하는 것은 일정합니다.
삽입 및 제거에 더 빠른 둘 중 어느 것이 발생하는지에 따라 다릅니다. 시작에 가까워지면 비교적 적은 수의 요소를 거쳐야하기 때문에 LinkedList가 더 빠릅니다. 우리가 끝에 가까워지면 ArrayList가 더 빠를 것입니다. 왜냐하면 우리는 일정한 시간에 도착하고 그 뒤에 나오는 몇 개의 나머지 요소 만 변경하면되기 때문입니다. 중간에서 정확하게 수행하면 n 개의 요소를 통과하는 것이 n 개의 값을 이동하는 것보다 빠르기 때문에 LinkedList가 더 빠릅니다.
보너스 : ArrayList에 대해이 두 가지 방법을 O (1)로 만드는 방법은 없지만 실제로 LinkedLists에는이를 수행하는 방법이 있습니다. 도중에 요소를 제거하고 삽입하는 전체 List를 살펴보고 싶다고 가정 해 봅시다. 일반적으로 LinkedList를 사용하여 각 요소의 맨 처음부터 시작합니다. 또한 Iterator로 작업중인 현재 요소를 "저장"할 수도 있습니다. Iterator의 도움으로 LinkedList에서 작업 할 때 remove () 및 insert ()에 대한 O (1) 효율성을 얻습니다. 그것이 LinkedList가 ArrayList보다 항상 좋은 곳이라는 것을 알고있는 유일한 성능 이점입니다.
ArrayList는 AbstractList를 확장하고 목록 인터페이스를 구현합니다. ArrayList는 동적 배열입니다.
기본적으로 배열
의 단점을 극복하기 위해 생성되었다고 말할 수 있습니다 . LinkedList 클래스는 AbstractSequentialList를 확장하고 List, Deque 및 Queue 인터페이스를 구현합니다.
성능
arraylist.get()
은 O (1)이지만 linkedlist.get()
O (n)
arraylist.add()
은 O (1)이고 linkedlist.add()
0 (1)
arraylist.contains()
은 O (n)이며 linkedlist.contains()
O (n)
arraylist.next()
은 O (1)이며 linkedlist.next()
O (1)
arraylist.remove()
은 O (n)입니다. 반면 linkedlist.remove()
에 O (1)
는 arraylist iterator.remove()
는 O (n)
이며, linkedlist iterator.remove()
는 O (1)입니다