Categories: Java

Java 자바 ArrayList 동작 원리와 사용법

ArrayList란 무엇인가?

ArrayList는 자바에서 가장 널리 사용되는 컬렉션 중 하나로, 가변 크기의 배열을 구현한 클래스입니다. 이는 배열과 비슷한 특성을 가지지만, 데이터의 동적 추가와 제거가 가능하다는 점에서 큰 장점을 지닙니다. 이 글에서는 ArrayList의 구조와 특성, 장단점, 사용 예제 등을 자세하게 다루어, 여러분이 ArrayList를 제대로 이해하고 활용할 수 있도록 도와드리겠습니다.

ArrayList는 자바의 java.util 패키지에 포함된 클래스로, 배열의 단점을 보완하여 더 유연하게 데이터를 관리할 수 있도록 도와줍니다. 기본 배열(array)은 생성 시 크기가 고정되며, 변경이 불가능한 반면 ArrayList는 크기가 자동으로 조정되어 데이터를 추가하거나 삭제할 수 있습니다. 이로 인해 ArrayList는 요소의 개수가 동적으로 변할 수 있는 상황에서 매우 유용하게 사용됩니다.

주요 특징

  • 동적 크기 조절: 요소가 추가되면 내부 배열의 크기가 자동으로 증가합니다.
  • 순차적 저장: 인덱스를 통해 요소에 직접 접근할 수 있는 특징을 지니며, 배열과 같은 방식으로 인덱싱을 통해 빠르게 데이터를 검색할 수 있습니다.
  • 중복 허용: ArrayList는 중복된 요소를 허용합니다.
  • 비동기적: ArrayList는 스레드 안전하지 않으며, 멀티스레드 환경에서는 동기화가 필요합니다.

ArrayList의 내부 구조

ArrayList는 내부적으로 배열을 사용하여 데이터를 관리합니다. 기본적으로 ArrayList의 초기 용량은 10이며, 만약 추가적인 요소를 저장해야 할 때 용량을 초과하게 되면, 내부 배열의 크기가 1.5배로 증가하게 됩니다. 이는 ensureCapacity()라는 메서드를 통해 이루어지며, 이 과정에서 새로운 배열이 생성되고 기존 요소들이 새로운 배열로 복사됩니다. 따라서 크기 조정이 자주 일어나면 성능 저하가 발생할 수 있습니다.

동작 원리

  • 초기 생성: ArrayList를 생성할 때 초기 용량을 설정할 수 있으며, 기본값은 10입니다.
  • 자동 확장: 새로운 요소가 추가되어 현재 용량을 초과할 경우, ArrayList는 새로운 배열을 만들어 현재 배열의 내용을 복사한 후 추가합니다. 이 과정은 O(n)의 시간 복잡도를 가지므로, 많은 데이터를 다룰 때 주의가 필요합니다.
Java
ArrayList<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
Java

위 코드에서 요소가 추가될 때마다 내부 배열의 크기가 적절히 조절되며, 이는 사용자가 명시적으로 크기를 관리할 필요 없이 동적으로 관리됩니다.

메모리 관리와 확장 전략

  • 확장 정책: ArrayList의 내부 배열 크기는 용량을 초과할 경우 기존 크기의 1.5배로 확장됩니다. 예를 들어, 초기 용량이 10일 때 11번째 요소가 추가되면 내부 배열은 15로 크기가 조정됩니다.
  • 메모리 낭비 최소화: 만약 요소의 수가 매우 줄어든 경우, trimToSize() 메서드를 사용하여 현재 사용 중인 요소 수에 맞게 용량을 조정할 수 있습니다. 이를 통해 메모리 낭비를 줄일 수 있습니다.
Java
list.trimToSize();
Java

이 메서드는 리스트의 크기를 현재 요소의 수에 맞게 조정하여 불필요한 메모리를 줄이는 데 유용합니다.

주요 메서드와 사용법

요소 추가 (add() 메서드)

ArrayList에 요소를 추가하는 가장 기본적인 메서드는 add()입니다. 이 메서드는 끝에 새로운 요소를 추가하며, 시간 복잡도는 평균적으로 O(1)입니다. 다만, 내부 배열의 크기를 변경해야 하는 상황에서는 O(n)의 복잡도를 가지게 됩니다.

Java
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
Java

특정 위치에 요소 추가 (add(index, element) 메서드)

특정 위치에 요소를 추가할 수도 있습니다. 이 경우 해당 인덱스 이후의 모든 요소들이 한 칸씩 뒤로 이동해야 하기 때문에 시간 복잡도는 O(n)입니다.

Java
numbers.add(1, 10);  // 인덱스 1에 값 10을 추가
Java

요소 접근 (get() 메서드)

get() 메서드를 사용하면 인덱스를 통해 특정 요소에 접근할 수 있습니다. 이는 배열처럼 O(1)의 시간 복잡도를 가지며, 빠르게 원하는 요소를 검색할 수 있습니다.

Java
int number = numbers.get(1);  // 결과: 10
Java

요소 수정 (set() 메서드)

특정 위치의 요소를 수정할 수 있습니다. 이는 O(1)의 시간 복잡도를 가지며, 요소의 값을 직접 변경합니다.

Java
numbers.set(1, 20);  // 인덱스 1의 값을 20으로 변경
Java

요소 제거 (remove() 메서드)

remove() 메서드는 특정 인덱스에 있는 요소를 제거하거나 특정 값을 가진 첫 번째 요소를 제거합니다. 이 경우 요소를 제거한 뒤 나머지 요소들을 앞으로 이동시키기 때문에, 최악의 경우 O(n)의 시간 복잡도를 가집니다.

Java
numbers.remove(1);  // 인덱스 1의 요소(20)를 제거
Java

리스트 크기 확인 (size() 메서드)

리스트에 포함된 요소의 수를 반환하는 size() 메서드는 O(1)의 시간 복잡도를 가집니다.

Java
int size = numbers.size();  // 현재 리스트 크기 반환
Java

리스트 비우기 (clear() 메서드)

모든 요소를 제거하고 리스트를 비웁니다. 시간 복잡도는 O(n)이며, 요소들을 한 번에 제거합니다.

Java
numbers.clear();  // 모든 요소 제거
Java

ArrayList의 장단점

장점
  1. 빠른 검색 속도: 인덱스를 이용한 요소 접근이 가능하므로, 검색의 시간 복잡도는 O(1)입니다.
  2. 동적 크기 조절: 배열과 달리, 크기를 미리 지정할 필요가 없어 데이터의 추가와 삭제가 용이합니다.
  3. 유연한 사용: 컬렉션 프레임워크의 다양한 메서드를 사용할 수 있어 데이터 관리가 용이합니다.
단점
  1. 느린 삽입과 삭제: 중간에 요소를 삽입하거나 제거할 때는 나머지 요소들을 이동시켜야 하므로, 최악의 경우 O(n)의 시간 복잡도를 가집니다.
  2. 메모리 낭비 가능성: 내부 배열의 크기가 증가할 때, 사용되지 않는 공간이 발생할 수 있어 메모리 낭비가 일어날 수 있습니다.
  3. 스레드 안전하지 않음: 기본적으로 ArrayList는 비동기적이므로, 멀티스레드 환경에서는 동기화 처리가 필요합니다.
효율적인 사용 방법
  • 초기 용량 설정: 많은 데이터를 다룰 경우, 예상되는 데이터 크기만큼 초기 용량을 설정하여 크기 조절에 드는 비용을 줄일 수 있습니다.
  • 빈번한 삽입/삭제 작업이 있는 경우: 중간에 데이터를 자주 삽입하거나 삭제해야 하는 경우 LinkedList가 더 적합할 수 있습니다
  • 멀티스레드 환경: 멀티스레드 환경에서 안전하게 사용하려면 Collections.synchronizedList()를 사용하여 동기화된 리스트로 변환할 수 있습니다.

ArrayList vs LinkedList

ArrayList와 LinkedList는 둘 다 List 인터페이스를 구현하고 있지만, 내부 구조와 특성에서 차이가 있습니다. ArrayList는 검색 속도가 빠르지만 삽입/삭제에 비효율적입니다. 반면 LinkedList는 삽입/삭제가 빠르지만 인덱스를 통한 접근이 느립니다. 따라서 읽기 작업이 많은 경우에는 ArrayList를, 삽입/삭제 작업이 빈번한 경우에는 LinkedList를 사용하는 것이 좋습니다.

특징ArrayListLinkedList
내부 구조동적 배열이중 연결 리스트
접근 속도O(1) (인덱스)O(n)
삽입/삭제 속도O(n)O(1) (앞뒤에서)
메모리 사용효율적추가적인 노드 오버헤드

ArrayList 사용 예제

다음은 ArrayList를 사용하여 학생들의 이름을 관리하는 간단한 예제입니다.

Java
import java.util.ArrayList;

public class StudentList {
    public static void main(String[] args) {
        ArrayList<String> students = new ArrayList<>();
        students.add("Minsu");
        students.add("Jihyun");
        students.add("Soyeon");

        // 학생 목록 출력
        for (String student : students) {
            System.out.println(student);
        }

        // 학생 제거
        students.remove("Jihyun");

        // 학생 목록 출력 (업데이트 후)
        System.out.println("\n업데이트된 학생 목록:");
        for (String student : students) {
            System.out.println(student);
        }
    }
}
Java

위 코드에서 ArrayList는 학생들의 이름을 동적으로 관리하는 데 사용됩니다. 요소를 추가하고 제거하는 작업이 간단하게 이루어지며, 리스트의 크기도 자동으로 조절됩니다.

결론

ArrayList는 자바에서 매우 유용한 자료구조로, 동적 크기 조절빠른 요소 접근의 장점을 가지고 있습니다. 하지만 삽입/삭제 작업에서는 성능 저하가 발생할 수 있기 때문에, 사용 상황에 따라 적절한 리스트 타입을 선택하는 것이 중요합니다. ArrayList는 읽기 작업이 빈번한 경우에 특히 유용하며, 자바 개발 시 가장 많이 사용되는 컬렉션 중 하나입니다.

ArrayList를 제대로 이해하고 사용하는 것은 자바 프로그래밍에서 매우 중요한 요소입니다. 위에서 설명한 개념과 내부 동작 원리를 잘 이해하고 나면, ArrayList를 적절히 활용하여 다양한 상황에서 효율적으로 데이터를 관리할 수 있을 것입니다.

추가적으로, ArrayList를 사용하는 멀티스레드 환경에서의 주의 사항과 성능 최적화 전략을 고려하여, 프로그램의 요구사항에 맞는 적절한 자료구조를 선택하는 것이 중요합니다.

suover

Recent Posts

Java 자바 제네릭(Generic) 개념과 문법 알아보기

제네릭(Generic)이란? 자바 제네릭은 코드의 재사용성을 높이고 타입 안전성을 보장하는 중요한 개념입니다. 이 블로그 글에서는 자바…

2개월 ago

Java 자바 예외(Exception) 처리와 개념 정리

예외란 무엇인가? 자바에서 예외(Exception)는 프로그램 실행 중에 발생하는 비정상적인 상황을 나타냅니다. 즉, 정상적인 프로그램 흐름에서…

2개월 ago

Java 자바 중첩 클래스(Nested Class)란?

중첩클래스란? 자바에서 중첩 클래스(Nested Class)는 자바 프로그래밍의 중요한 개념 중 하나로, 클래스 안에 다른 클래스를…

2개월 ago

Java 자바 Enum 열거형 사용법

Enum이란? Enum은 "enumeration"의 줄임말로, 특정 값들의 집합을 정의할 수 있는 특별한 클래스입니다. 예를 들어, 요일,…

2개월 ago

Java 자바 래퍼 클래스(Wrapper Class)란? 래퍼 클래스의 필요성

래퍼 클래스(Wrapper Class)란? 자바에서 래퍼 클래스는 기본 데이터 타입(Primitive Data Type)을 객체(Object)로 감싸는 클래스입니다. 자바의…

2개월 ago

Java 자바 StringBuilder 개념 및 특징 주요 메소드

서론 Java에서 문자열을 다룰 때, 문자열의 변경이 빈번하게 발생하는 경우라면 StringBuilder 클래스를 사용하는 것이 매우…

3개월 ago