본문 바로가기
Java

[Java] ArrayList 내부 구조 파헤치기(크기, 메모리 구조, 내부 동작 과정)

by 하부루 2024. 7. 30.

1. ArrayList란?

ArrayList는 Java 컬렉션 프레임워크의 일부로, 가변 길이 배열을 구현한 클래스

ArrayList는 순차적으로 데이터를 저장하며, 저장된 요소는 인덱스를 통해 접근할 수 있음

  • 동적 배열
    • ArrayList는 동적으로 크기가 조정되는 배열
    • 요소를 추가하거나 제거할 때 크기가 자동으로 조정
  • 인덱스 접근
    • 배열처럼 인덱스를 사용해 요소에 접근할 수 있음
  • 중복 허용
    • ArrayList는 중복된 요소를 허용
  • 비동기적
    • ArrayList는 기본적으로 비동기적
    • 멀티스레드 환경에서 동기화를 지원하지 않음
// 기본 생성
ArrayList<String> list = new ArrayList<>();

// 초기 용량을 지정해 생성
ArrayList<Integer> list = new ArrayList<>(10);
 
// 주요 메서드
add(E e) : 요소를 리스트에 추가
add(int index, E element) : 특정 위치에 요소를 추가
get(int index) : 특정 위치의 요소를 반환
set(int index, E element) : 특정 위치의 요소를 새로운 요소로 대체

remove(int index) : 특정 위치의 요소를 제거
remove(Object o) : 특정 요소를 리스트에서 제거

size() : 리스트의 요소 개수를 반환
clear() : 리스트의 모든 요소를 제거
isEmpty() : 리스트가 비어있는지 확인합
contains(Object o) : 특정 요소가 리스트에 포함되어 있는지 확인

2. ArrayList의 크기

  • ArrayList는 내부적으로 요소를 저장하기 위해 배열을 사용
  • 초기에는 고정된 크기의 배열(10)이 생성되며, 요소가 추가됨에 따라 크기가 변경
  • ArrayList에 요소가 추가되어 현재 크기를 초과하면, ArrayList는 더 큰 배열을 할당하고, 기존의 요소를 새로운 배열로 복사 함
ArrayList<Integer> arrList = new ArrayList<>(10);
→ ArrayList 생성시 크기를 10으로 지정했기때문에 초기 크기는 10

ArrayList<Integer> arrList = new ArrayList<>();
→ ArrayList 생성시 크기를 지정하지 않았지만 초기 크기는 10

* private static final int DEFAULT_CAPACITY = 10;
ArrayList 설정 중에 초기 크기 설정하는 상수

크기가 10인 ArrayList에 요소를 더 추가하면 grow() 메서드를 통해서
ArrayList의 크기는 11이 되는게 아닌 * 1.5의 값인 15가 됨
arrList = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
null, null, null, null, null}
//ArrayList 추가 용량 지정 메서드
private void grow(int minCapacity) {

    // 현재 배열의 용량
    int oldCapacity = elementData.length;
    
    // 1.5배 증가된 새로운 용량 계산
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
        
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
        
    // 새로운 용량의 배열로 복사
    elementData = Arrays.copyOf(elementData, newCapacity);
}


3. ArrayList의 메모리 구조

  • ArrayList의 내부 배열은 Heap 영역에 할당
  • 배열 참조는 ArrayList 객체가 Stack 영역에서 유지
  • 요소는 배열의 index를 통해 접근 가능


4. ArrayList의 주요 메서드 동작 과정

1. get( )

ex) numberList.get(2);

  • numberList에서 인덱스 2번값 호출
  • checkIndex( ) 메서드로 현재 호출한 인덱스 번호가 전체 크기를 초과하는지 체크
  • elementData( ) 메서드로 호출한 인덱스 값 리턴

2. set( )

ex) nameList.set(2, “홍길동”);

  • nameList에서 인덱스 2번값에 “홍길동”을 삽입
  • checkIndex( ) 메서드로 현재 호출한 인덱스 번호가 전체 크기를 초과하는지 체크
  • elementData( ) 메서드로 호출한 인덱스 값을 oldValue에 저장
  • elementData[index] 로 새로 입력한 “홍길동”을 삽입해 데이터 변경
  • oldValue를 리턴

3. add( )

exampleList.add(5, “홍길동”);

public void add(int index, E element) {
    // 1. 인덱스 범위 확인
    rangeCheckForAdd(index);

    // 2. 수정 횟수 증가 (ConcurrentModificationException을 방지하기 위함)
    modCount++;

    // 3. 현재 리스트의 크기를 저장
    final int s;
    Object[] elementData;
    if ((s = size) == (elementData = this.elementData).length) {
        // 4. 현재 크기가 배열의 길이와 같으면 배열 크기 증가
        elementData = grow();
    }

    // 5. 요소 복사: index 위치부터 끝까지 요소를 한 칸씩 오른쪽으로 이동
    System.arraycopy(elementData, index, elementData, index + 1, s - index);

    // 6. 새로운 요소 삽입
    elementData[index] = element;

    // 7. 리스트 크기 증가
    size = s + 1;
}

exampleList.add(“홍길동”);

  • modCount++로 수정 횟수 증가
  • 다른 add( ) 메서드 호출 (추가할 데이터, 배열, 사이즈가 매개변수인 add( ) 메서드)
  • 배열의 크기를 +1 증가시키고 배열의 마지막 요소에 새로운 값을 넣음

4. remove( )

example.remove(2);

  • checkIndex 메서드로 인덱스 번호가 유효한지 체크
  • 삭제할 인덱스의 값을 oldValue에 저장
  • fastRemove 메서드 호출
  • modCount++로 수정 횟수 증가
  • arraycopy 메서드로 현재 수정된 인덱스 이후의 값들을 한 칸씩 왼쪽으로 이동
  • es[size = newSize] = null 로 마지막 요소를 null로 변경

example.remove(”홍길동”);