- 순서가 있고 중복을 허용하는 자료구조
- 자바의 컬렉션 프레임워크가 제공하는 가장 대표적인 자료 구조가 리스트

Collection 인터페이스는 java.util 패키지의 컬렉션 프레임워크의 핵심 인터페이스 중 하나collection 인터페이스는 List, Set, Queue와 같은 다양한 하위 인터페이스와 함께 사용되며, 이를 통해 데이터를 리스트, 세트, 큐 등의 형태로 관리할 수 있음List 인터페이스는 java.util 패키지에 있는 컬렉션 프레임워크의 일부임List는 객체들의 순서가 있는 컬렉션을 나타내며, 같은 객체의 중복 저장을 허용함List 인터페이스는 ArrayList, LinkedList와 같은 여러 구현 클래스를 가지고 있으며, 각 클래스는 List 인터페이스의 메서드를 구현함
java.util.ArrayListArrayList는 우리가 직접 만든 MyArrayList와 거의 비슷함배열을 사용해서 데이터를 관리함
기본 CAPACITY는 10임(DEFAULT_CAPACITY = 10)
CAPACITY를 넘어가면 배열을 50% 증가함10 -> 15 -> 22 -> 33 -> 49로 증가함메모리 고속 복사 연산을 사용함
ArrayList의 중간 위치에 데이터를 추가하면, 추가할 위치 이후의 모든 요소를 한 칸씩 뒤로 이동시켜야 함ArrayList는 이 부분을 최적화함System.arraycopy()를 사용함데이터 추가 - 메모리 고속 복사 연산 사용

시스템 레벨에서 배열을 한 번에 아주 빠르게 복사함
이 부분은 OS, 하드웨어에 따라 성능이 다르기 때문에 정확한 측정이 어렵지만, 한 칸씩 이동하는 방식과 비교하면 보통 수 배 이상의 빠른 성능을 제공함
java.util.LinkedListLinkedList는 우리가 직접 만든 MyLinkedList와 거의 비슷함
- 이중 연결 리스트 구조
- 첫 번째 노드와 마지막 노드 둘 다 참조

MyLinkedList의 노드는 다음 노드로만 이동할 수 있는 단일 연결 구조
LinkedList는 이중 연결 구조를 사용함class Node {
E item;
Node next;
Node prev;
}
class LinkedList {
Node first; //첫 번째 노드 참조
Node last; //마지막 노드 참조
int size;
}
이 구조는 다음 노드 뿐만 아니라 이전 노드로도 이동할 수 있음
node.next를 호출하면 다음 노드로, node.prev를 호출하면 이전 노드로 이동함마지막 노드에 대한 참조를 제공함
O(1)의 성능을 제공함이전 노드로 이동할 수 있기 때문에 마지막 노드부터 앞으로, 즉 역방향으로 조회할 수 있음
package collection.list;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class JavaListPerformanceTest {
public static void main(String[] args) {
int size = 50_000;
System.out.println("==ArrayList 추가==");
addFirst(new ArrayList<>(), size);
addMid(new ArrayList<>(), size);
ArrayList<Integer> arrayList = new ArrayList<>(); //조회용 데이터로 사용
addLast(arrayList, size);
System.out.println("==LinkedList 추가==");
addFirst(new LinkedList<>(), size);
addMid(new LinkedList<>(), size);
LinkedList<Integer> linkedList = new LinkedList<>(); //조회용 데이터로 사용
addLast(linkedList, size);
int loop = 10000;
System.out.println("==ArrayList 조회==");
getIndex(arrayList, loop, 0);
getIndex(arrayList, loop, size / 2);
getIndex(arrayList, loop, size - 1);
System.out.println("==LinkedList 조회==");
getIndex(linkedList, loop, 0);
getIndex(linkedList, loop, size / 2);
getIndex(linkedList, loop, size - 1);
System.out.println("==ArrayList 검색==");
search(arrayList, loop, 0);
search(arrayList, loop, size / 2);
search(arrayList, loop, size - 1);
System.out.println("==LinkedList 검색==");
search(linkedList, loop, 0);
search(linkedList, loop, size / 2);
search(linkedList, loop, size - 1);
}
private static void addFirst(List<Integer> list, int size) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
list.add(0, i);
}
long endTime = System.currentTimeMillis();
System.out.println("앞에 추가 - 크기: " + size + ", 계산 시간: " + (endTime - startTime) + "ms");
}
private static void addMid(List<Integer> list, int size) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
list.add(i / 2, i);
}
long endTime = System.currentTimeMillis();
System.out.println("평균 추가 - 크기: " + size + ", 계산 시간: " + (endTime - startTime) + "ms");
}
private static void addLast(List<Integer> list, int size) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
list.add(i);
}
long endTime = System.currentTimeMillis();
System.out.println("뒤에 추가 - 크기: " + size + ", 계산 시간: " + (endTime - startTime) + "ms");
}
private static void getIndex(List<Integer> list, int loop, int index) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < loop; i++) {
list.get(index);
}
long endTime = System.currentTimeMillis();
System.out.println("index: " + index + ", 반복: " + loop + ", 계산 시간: " + (endTime - startTime) + "ms");
}
private static void search(List<Integer> list, int loop, int findValue) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < loop; i++) {
list.indexOf(findValue);
}
long endTime = System.currentTimeMillis();
System.out.println("findValue: " + findValue + ", 반복: " + loop + ", 계산 시간: " + (endTime - startTime) + "ms");
}
}
실행 결과
==ArrayList 추가==
앞에 추가 - 크기: 50000, 계산 시간: 106ms
평균 추가 - 크기: 50000, 계산 시간: 49ms
뒤에 추가 - 크기: 50000, 계산 시간: 1ms
==LinkedList 추가==
앞에 추가 - 크기: 50000, 계산 시간: 2ms
평균 추가 - 크기: 50000, 계산 시간: 1116ms
뒤에 추가 - 크기: 50000, 계산 시간: 2ms
==ArrayList 조회==
index: 0, 반복: 10000, 계산 시간: 1ms
index: 25000, 반복: 10000, 계산 시간: 0ms
index: 49999, 반복: 10000, 계산 시간: 1ms
==LinkedList 조회==
index: 0, 반복: 10000, 계산 시간: 0ms
index: 25000, 반복: 10000, 계산 시간: 439ms
index: 49999, 반복: 10000, 계산 시간: 1ms
==ArrayList 검색==
findValue: 0, 반복: 10000, 계산 시간: 0ms
findValue: 25000, 반복: 10000, 계산 시간: 104ms
findValue: 49999, 반복: 10000, 계산 시간: 218ms
==LinkedList 검색==
findValue: 0, 반복: 10000, 계산 시간: 1ms
findValue: 25000, 반복: 10000, 계산 시간: 473ms
findValue: 49999, 반복: 10000, 계산 시간: 945ms

자바의 배열 리스트는 이때 메모리 고속 복사를 사용하기 때문에 성능이 최적화됨
메모리 고속 복사는 시스템에 따라 성능이 다르기 때문에 정확한 계산은 어렵지만 대략 O(n/10) 정도로 추정
O(n)이 됨이론적으로 LinkedList의 중간 삽입 연산은 ArrayList보다 빠를 수 있음
ArrayList는 요소들이 메모리 상에서 연속적으로 위치하여 CPU 캐시 효율이 좋고, 메모리 접근 속도가 빠름
반면에 LinkedList는 각 요소가 별도의 객체로 존재하고 다음 요소의 참조를 저장하기 때문에 CPU 캐시 효율이 떨어지고, 메모리 접근 속도가 상대적으로 느려질 수 있음
ArrayList의 경우 CAPACITY를 넘어서면 배열을 다시 만들고 복사하는 과정이 추가됨
50%씩 늘어나기 때문에 이 과정은 가끔 발생하므로, 전체 성능에 큰 영향을 주지는 않음정리하면 이론적으로
LinkedList가 중간 삽입이 있어 더 효율적일 수 있지만,
현대 컴퓨터 시스템의 메모리 접근 패턴, CPU 캐시 최적화, 메모리 고속 복사 등을 고려할 때
ArrayList가 실제 사용 환경에서 더 나은 성능을 보여주는 경우가 많음
- 대부분의 경우 배열 리스트가 성능상 유리함
- 이런 이유로 실무에서는 주로 배열 리스트를 기본으로 사용함
- 만약 데이터를 앞쪽에서 자주 추가하거나 삭제할 일이 있다면 연결 리스트를 고려