Array vs ArrayList vs LinkedList
한눈에 보기
배열, ArrayList, LinkedList의 내부 저장 방식과 연산별 시간 복잡도를 비교한다. 캐시 지역성 때문에 실무에서 ArrayList가 유리한 이유와 상황별 선택 기준을 정리했다.
최종 업데이트 2026.09.17
배열(Array), 어레이리스트(ArrayList), 연결 리스트(LinkedList)는 모두 데이터를 순서대로 담는 선형 자료구조지만 내부 저장 방식이 달라 성능 특성이 크게 갈린다. 배열은 크기가 고정된 연속 메모리, ArrayList는 자동으로 확장되는 동적 배열, LinkedList는 노드를 포인터로 이은 연결 구조다. 어떤 연산이 자주 일어나는지에 따라 세 구조의 유불리가 달라지므로, 각 구조의 내부 동작을 이해하고 상황에 맞게 선택하는 능력이 중요하다.
세 구조의 내부 동작
배열은 선언 시점에 크기가 정해지고 그 크기만큼의 연속 메모리를 확보한다. 크기를 바꾸려면 새 배열을 만들어 직접 복사해야 한다. 타입이 고정되어 있어 오버헤드가 가장 작고, 자바(Java)에서는 기본 타입을 박싱(boxing) 없이 그대로 담을 수 있다는 점도 배열만의 장점이다.
ArrayList는 배열을 감싼 클래스다. 내부에 여유 용량(capacity)을 가진 배열을 두고, 요소가 가득 차면 더 큰 배열을 새로 할당해 기존 요소를 복사한다. 자바의 ArrayList는 용량을 약 1.5배씩 키운다. 이 확장 비용은 가끔만 발생하므로 끝에 추가하는 연산의 분할 상환(amortized) 비용은 O(1)이다. 사용자 입장에서는 크기 제한 없는 배열처럼 쓸 수 있다.
LinkedList는 값과 포인터를 담은 노드(node)들이 메모리 곳곳에 흩어져 있고, 각 노드가 다음 노드(이중 연결이면 이전 노드도)를 가리키는 구조다. 자바의 LinkedList는 이중 연결 리스트(doubly linked list)로 구현되어 있어 머리와 꼬리 양쪽에서 O(1)로 삽입과 삭제가 가능하다. 대신 인덱스로 접근하려면 앞이나 뒤에서부터 포인터를 따라가야 하므로 O(n)이 든다.
연산별 시간 복잡도 비교
| 연산 | Array | ArrayList | LinkedList |
|---|---|---|---|
| 인덱스 접근 | O(1) | O(1) | O(n) |
| 끝에 삽입 | 불가(고정 크기) | 분할 상환 O(1) | O(1) |
| 맨 앞에 삽입 | O(n) 이동 필요 | O(n) | O(1) |
| 중간 삽입/삭제 | O(n) | O(n) | 위치 탐색 O(n) + 연결 변경 O(1) |
| 값 탐색 | O(n) | O(n) | O(n) |
| 메모리 오버헤드 | 없음 | 여유 용량만큼 | 노드마다 포인터 2개와 객체 헤더 |
표에서 주의할 점이 하나 있다. LinkedList의 중간 삽입이 O(1)이라고 흔히 말하지만, 이는 삽입할 위치의 노드를 이미 쥐고 있을 때의 이야기다. 인덱스로 위치를 찾는 과정까지 포함하면 전체 비용은 O(n)이 되어 ArrayList와 다르지 않다.
이론과 실제 성능의 차이
시간 복잡도만 보면 삽입과 삭제가 많은 상황에서 LinkedList가 유리해 보이지만, 실무에서는 대부분의 경우 ArrayList가 더 빠르다. 이유는 캐시 지역성(cache locality)이다. ArrayList의 요소는 연속 메모리에 있어 순회할 때 CPU 캐시 적중률이 높은 반면, LinkedList의 노드는 힙(heap) 곳곳에 흩어져 있어 노드를 옮겨갈 때마다 캐시 미스(cache miss)가 발생하기 쉽다.
또한 LinkedList는 노드마다 객체 헤더와 앞뒤 포인터를 추가로 저장하므로 같은 데이터를 담아도 메모리를 훨씬 많이 쓴다. 삽입 시에도 매번 노드 객체를 새로 할당하므로 가비지 컬렉션(garbage collection) 부담이 커진다. 이런 이유로 자바 진영에서는 특별한 근거가 없다면 기본 선택지를 ArrayList로 두는 것이 통설이다.
코드 예시
다음 자바 코드는 같은 List 인터페이스로 두 구현체를 다루는 모습이다. 인터페이스는 같아도 각 호출의 비용이 다르다는 점이 핵심이다.
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();
arrayList.add(1); // 분할 상환 O(1)
arrayList.get(0); // O(1)
arrayList.add(0, 99); // O(n), 전체 요소를 한 칸씩 민다
linkedList.addFirst(1); // O(1), 머리 포인터만 바꾼다
linkedList.get(500); // O(n), 노드를 500번 따라간다
선택 기준 정리
데이터 개수가 확정되어 있고 최고의 성능과 최소의 메모리가 필요하면 배열을 쓴다. 크기를 예측하기 어렵고 조회가 많다면 ArrayList가 기본 선택이다. 리스트의 양 끝에서 삽입과 삭제가 집중적으로 일어나는 덱(Deque)이나 큐(Queue) 용도라면 LinkedList가 이론상 적합하지만, 자바에서는 이 경우에도 배열 기반 ArrayDeque가 더 빠른 경우가 많다는 점까지 알아두면 좋다. 결국 선택의 기준은 자료구조 이름이 아니라 지배적인 연산이 무엇인지, 그리고 캐시와 메모리 오버헤드까지 고려한 실측 성능이 어떤지다.
면접에서 이렇게 물어본다
- ArrayList와 LinkedList의 내부 구조 차이를 설명하고, 각각 어떤 상황에서 유리한지 말해 보라.
- LinkedList의 중간 삽입이 O(1)이라는 말이 왜 절반만 맞는 말인지 설명해 보라.
- 시간 복잡도상 유리해 보이는 LinkedList가 실무에서 ArrayList보다 느린 경우가 많은 이유는 무엇인가.
- ArrayList가 가득 찼을 때 내부에서 어떤 과정이 일어나는지 설명해 보라.
- 큐를 구현할 때 LinkedList 대신 ArrayDeque를 권하는 이유를 설명해 보라.
자료구조에서 같이 읽기
- 배열 (Array)같은 타입의 데이터를 연속 메모리에 저장하는 기본 선형 자료구조인 배열을 다룬다. O(1) 인덱스 접근의 원리, 연산별 시간 복잡도, 동적 배열의 확장과 분할 상환 분석, 캐시 지역성까지 정리했다.
- AVL 트리와 레드 블랙 트리 (AVL Tree & Red-Black Tree)자가 균형 이진 탐색 트리인 AVL 트리와 레드 블랙 트리를 비교한다. 균형 인수와 회전, 탐색과 삽입 비용, TreeMap이 레드 블랙 트리를 쓰는 이유를 정리했다.
- B 트리와 B+ 트리 (B-Tree & B+ Tree)한 노드에 여러 키를 담아 높이를 낮춘 B 트리와 리프에 데이터를 모아 연결한 B+ 트리를 다룬다. 디스크 I/O 최소화 원리와 데이터베이스 인덱스가 B+ 트리를 쓰는 이유를 정리했다.
- 이진 탐색 트리 (Binary Search Tree)왼쪽은 작은 값, 오른쪽은 큰 값이라는 규칙으로 평균 O(log n) 탐색을 만드는 이진 탐색 트리를 다룬다. 삽입과 삭제 과정, 편향 시 퇴화 문제와 자가 균형 트리, 해시 테이블과의 선택 기준을 정리했다.
이 개념, 내 코드에서는 어떻게 물어볼까요
데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.
공고 넣고 시작하기