티스토리 뷰
List
List Interface(리스트 인터페이스)는 대표적인 선형 자료구조로 주로 순서가 있는 데이터를 목록으로 이용할 수 있도록 만들어진 인터페이스다. 우리가 배열을 사용할 때 int[] array = new int[10];처럼 사용하지만 이러한 경우는 10개의 공간 외에는 더이상 사용하지 못한다. 만약 array[13] = 30;가 실행되면 할당된 크기(범위) 밖이기 때문에 IndexOutofBoundsException라는 에러가 발생한다.
이러한 단점을 보완하여 List를 통해 구현된 클래스들은 '동적 크기'를 갖으며 배열처럼 사용할 수 있게 되어있다. 한 마디로 배열의 기능 + 동적 크기 할당이 합쳐져 있다고 보면 된다.
List Interface에 선언된 대표적인 메서드
메서드 | 리턴 타입 | 설명 |
add(E e) | boolean | 요소를 추가한다. |
remove(Object o) | boolean | 지정한 객체와 같은 첫 번째 객체를 삭제한다. |
contains(Object o) | boolean | 지정한 객체가 컬렉션에 있는지를 확인한다. (있을 경우 true, 없을 경우 false) |
size() | int | 현재 컬렉션에 있는 요소 개수를 반환한다. |
get(int index) | E | 지정된 위치에 저장된 원소를 반환한다. |
set(int index, E elements) | E | 지정된 위치에 있는 요소를 지정된 요소로 바꾼다. |
isEmpty() | boolean | 현재 컬렉션에 요소가 없다면 true를, 요소가 존재한다면 false를 반환한다. |
equals(Object o) | boolean | 지정된 객체와 같은지 비교한다. |
indexOf(Object o) | int | 지정된 객체가 있는 첫 번째 요소의 위치를 반환한다. 만일 없을경우 -1을 반환한다. |
clear() | void | 모든 요소들을 제거한다. |
List와 Array의 차이점
동일한 특성의 데이터를 묶고, 반복문(loop) 내에 변수를 이용하여 하나의 묶음 데이터들을 모두 접근할 수 있는 공통점이 있지만 다음과 같은 차이점이 있다.
Array
- 처음 선언한 배열의 크기(길이)는 변경할 수 없다. 이를 정적 할당(static allocation)이라고 한다.
- 메모리에 연속적으로 나열되어 할당된다.
- index에 위치한 하나의 데이터(element)를 삭제하더라도 해당 index에는 빈공간으로 계속 남는다.
List
- 리스트의 길이가 가변적이다. 이를 동적 할당(dynamic allocation)이라고 한다.
- 데이터들이 연속적으로 나열된다.(메모리에 연속적으로 나열되지 않고 각 데이터들은 주소(reference)로 연결되어있다.)
- 데이터(element) 사이에 빈 공간을 허용하지 않는다.
ArrayList
ArrayList는 Object[] 배열을 사용하면서 내부 구현을 통해 동적으로 관리를 한다. 우리가 흔히 쓰는 Primitive 배열(ex. int[])과 유사한 형태라고 보면 된다. 하지만 Array와는 달리 데이터가 추가하는대로 필요시 2배로 사이즈가 늘어난다. 즉, 최상위 타입인 Object 타입으로 배열을 생성하여 사용하기 때문에 요소 접근(access elements)에서는 탁월한 성능을 보인다. 그 이유는 사이즈는 늘어나지만 검색할 때는 고정된 배열크기에서 검색을 하는 덕분에 O(1)이다. 하지만 중간의 요소가 삽입, 삭제가 일어나는 경우 그 뒤의 요소들은 한 칸씩 밀어야 하거나 당겨야 하기 때문에 삽입, 삭제에서는 비효율적인 모습을 보인다.
ArrayList 구현하기
public class MyArrayList {
private Object[] data;
private int size; // 배열의 크기
private int index; // 다음 데이터를 추가할 위치를 기억
public MyArrayList() {
this.size = 1; // MyArrayList 객체를 생성하면 먼저 사이즈 1로 할당
this.data = new Object[this.size]; // size만큼 배열크기 생성
this.index = 0;
}
public void add(Object obj) {
System.out.println("index: " + index + ", size: " + size + ", datasize: " + data.length + ", obj: " + obj);
// 추가 전에 배열 크기가 다 찼는지 확인
if (index == size - 1) {
doubling();
}
data[this.index] = obj;
index++;
}
public Object get(int i) throws Exception {
if (i > index - 1) {
throw new Exception("ArrayIndexOutOfBound");
} else if (i < 0) {
throw new Exception("Negative Value");
}
return data[i];
}
public void remove(int i) throws Exception {
if (i > index - 1) {
throw new Exception("ArrayIndexOutOfBound");
} else if (i < 0) {
throw new Exception("Negative Value");
}
// 삭제할 데이터를 기준으로 한 칸씩 앞으로 shift해서 빈 자리를 메꿔줌
for (int x = i; x < data.length - 1; x++) {
data[x] = data[x + 1];
}
index--;
}
private void doubling() {
size = size * 2;
Object[] newData = new Object[this.size];
// 새로운 배열에 기존 배열에 있는 데이터 복사하는 작업
for(int i = 0; i < data.length; i++) {
newData[i] = data[i];
}
this.data = newData;
}
}
ArrayList는 필요시 사이즈가 늘어날 때 2배 큰 크기의 새로운 배열을 선언하고, 그 배열에 기존 배열에 있었던 데이터 전부 를 새로운 배열에 복사하는 작업을 하는데 이를 Doubling이라고 한다. 이 Doubling에 소요되는 시간은 기존에 가지고 있었던 데이터를 N이라고 할 때 O(N)의 시간이 소요된다. 이런 번거로운 작업에도 불구하고, ArrayList의 입력시간은 여전히 O(1)이다. 그 이유는 Doubling을 할 때 그 전에 있던 데이터는 절반이기 때문에
MyArrayList 실행
public class Test {
public static void main(String[] args) throws Exception {
MyArrayList arrayList = new MyArrayList();
arrayList.add("0");
arrayList.add("1");
arrayList.add("2");
arrayList.add("3");
arrayList.add("4");
arrayList.add("5");
arrayList.add("6");
arrayList.add("7");
arrayList.add("8");
arrayList.add("9");
System.out.println(arrayList.get(5));
arrayList.remove(5);
System.out.println(arrayList.get(5));
}
}
위에서 설명했듯이 만약 데이터를 추가할 때 모두 차면 새로운 데이터 배열에 기존 데이터를 복사해야하기 때문에 O(N)이 시간이 걸리게 되고, 그 외에는 O(1)의 시간이 걸리게 된다.
LinkedList
LinkedList는 데이터(item)와 주소로 이루어진 클래스를 만들어 서로 연결하는 방식이다. 데이터와 주소로 이루어진 클래스를 Node(노드)라고 하는데, 각 노드는 이전의 노드와 다음 노드를 연결하는 방식이다. LinkedList의 경우 ArrayList와 가장 큰 차이점이라고 한다면 '노드'라는 객체를 이용하여 연결한다는 것이다.
ArrayList는 최상위 타입인 오브젝트 배열(Object[])을 사용하여 데이터를 담아두었다면, LinkedList는 배열을 이용하는 것이 아닌 하나의 객체를 두고 그 안에 데이터와 다른 노드를 가리키는 레퍼런스 데이터로 구성하여 여러 노드를 하나의 체인(chain)처럼 연결하는 것이다.
자세한 LinkedList에 대한 설명은 아래 블로그에서 보자.
LinkedList(SinglyLinkedList vs DoublyLinkedList)
LinkedList LinkedList란 Collection 프레임워크의 일부이며 java.util 패키지에 소속되어 있다. 이 클래스는 데이터가 연속된 위치에 저장되지 않고 모든 데이터가 데이터 영역과 주소 영역을 별도로 가지
oneny.tistory.com
ArrayList와 LinkedList 시간 복잡도 차이
작업 | 메서드 | ArrayList | LinkedList |
add at last index | add() | O(1) | O(1) |
add at given index | add(index, value) | O(N) | O(1) |
remove by index | remove(index) | O(N) | O(1) |
remove by value | remove(value) | O(N) | O(1) |
get by index | get(index) | O(1) | O(N) |
search by value | indexOf(value)0 | O(N) | O(N) |
ArrayList와 LinkedList 비교 측정하기
public class CompareArrayListLinkedList {
public static void main(String[] args) {
long arrayListStart = System.currentTimeMillis();
List<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
arrayList.add(0, i);
}
long arrayListFinish = System.currentTimeMillis() - arrayListStart;
long linkedListStart = System.currentTimeMillis();
List<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 1_000_000; i++) {
linkedList.add(0, i);
}
long linkedListFinish = System.currentTimeMillis() - linkedListStart;
System.out.println("arrayListFinish = " + arrayListFinish);
System.out.println("linkedListFinish = " + linkedListFinish);
}
}
ArayList를 1,000,000번 0번 인덱스에 넣는 작업을 한 결과 52.266초가 나오고, LinkedList에도 마찬가지로 0번 인덱스에 넣는 작업을 한 결과 0.063초가 나온것을 확인할 수 있다. ArrayList는 삽입하고 뒤 엘리먼트들을 모두 미는 작업도 해야하는 반면, LinkedList는 새로 들어온 노드를 head로 바꿔주고 next로 이전 head를 가리키는 작업만 하면 되기 때문에 성능에 차이가 있는 것을 확인할 수 있다.
ArrayList와 LinkedList 비교 측정 한계점
성능을 테스트하고 싶을 때는 startTime과 finishTime을 측정해서 테스트 결과를 비교하는데 로직 성능만을 검사하고 싶지만 warm up, 스레드 갯수 등 고려해야할 사항들이 많다. 이러한 부분을 해결해주기 위한 성능 측정 프레임워크인 벤치마크 프레임워크를 사용해서 로직에 대한 성능을 위해 부수적인 것들을 줄일 수 있는데 이에 대한 내용은 아래 글을 작성했으니 참고하자.
JMH로 warm up 후 테스트하기
Java에서 warm up을 하는 이유에 대해서 살펴보기 위해서는 JVM의 특성에 대해서 알아볼 필요가 있다. JVM 작성된 Java 코드에 대해 1차적으로 중간언어로 컴파일을 해야한다. 주로 Byte Code는 jar이나 war
oneny.tistory.com
출처
자바[JAVA] - 자바 컬렉션 프레임워크(Java Collections Framework)
자바[JAVA] - List Inteface(리스트 인터페이스)
자바[JAVA] - Singly LinkedList(단일 연결리스트) 구현하기
'Java > Java' 카테고리의 다른 글
GC(Garbage Collection), GC는 어떻게 대상 선정할까? (0) | 2023.07.17 |
---|---|
Java의 소수점 계산 오류 및 해결 (0) | 2023.07.16 |
Java의 hashCode, equals와 hashCode 같이 써야하는 이유 (0) | 2023.07.10 |
String Literal vs new String (0) | 2023.07.09 |
JVM의 Stack&Heap 이해하기 (0) | 2023.07.08 |
- Total
- Today
- Yesterday
- Synchronized
- 카프카
- 스프링 네티
- spring session
- mdcfilter
- 구름톤 챌린지
- transaction
- 비관적 락
- pessimistic lock
- 넥스트스탭
- 트랜잭션
- postgresql
- 자바 네티
- 네티 스레딩 모델
- nginx
- TDD
- socket
- Java
- sql
- spring webflux
- Kafka
- nginx configuration
- mysql
- annotation
- redis session
- NeXTSTEP
- 구름톤챌린지
- jvm 메모리 구조
- 람다
- 분산 락
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |