Java
[Java] ArrayList 내부 구조 파헤치기(크기, 메모리 구조, 내부 동작 과정)
하부루
2024. 7. 30. 17:52
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(”홍길동”);