👻 Intro.
💡 기본적인 자료 구조 용어에서 ArrayList는 일반적으로 동적 배열을 기반으로 하는 리스트를 의미한다. 이는 내부에서 배열을 사용하며, 필요에 따라(데이터의 추가나 삭제) 자동으로 크기를 조절할 수 있는 자료 구조를 나타낸다.
C# .NET Framework에서 제공하는 컬렉션 타입의 ArryList
ArrayList (System.Collections.ArrayList)
타입 안정성 : 비제네릭 컬렉션으로서, 모든 타입의 객체를 저장할 수 있다. 이는 타입 변환 오류를 유발할 수 있다. (ex. ArrayList에 저장된 객체를 다른 타입으로 변환할 때 런타임 오류가 발생할 가능성이 있음.)
저장된 객체를 사용하려면 해당 객체를 원래의 타입으로 박싱(Boxing) 또는 언박싱(Unboxing) 해야 한다. → 추가적인 오버헤드를 발생
List<T> (System.Collections.Generic.List<T>)
타입 안정성 : 제네릭 컬렉션으로서, 특정 타입의 객체만 저장할 수 있다. 이는 컴파일 시점에 타입 안정성을 제공하므로, 잘못된 타입의 객체를 추가하려고 시도하면 컴파일 오류가 발생
박싱/언박싱 과정이 필요하지 않기 때문에 성능상의 이점이 있다.
List<T>는 타입-특정 메서드 및 기능들을 제공. (해당 타입T에 특화된 메서드와 기능들을 사용할 수 있다는 것을 의미)
- 대부분의 경우 List<T>를 사용하는 것이 좋다. (타입 안정성과 성능상의 이점)
- ArrayList는 오래된 코드베이스 또는 .NET Framework의 초기 버전과의 호환성이 필요한 경우에만
✨ List<T>
- 제네릭 컬렉션: T는 타입 파라미터(int와 string 등은 T에 대한 구체적인 타입 인자)로, List<T>를 사용할 때 실제 데이터 타입을 명시한다. 예를 들어, List<int>는 정수를 저장하고, List<string>은 문자열을 저장한다.
- 동적 크기: List<T>의 크기는 동적이다. 초기에는 작은 용량으로 시작하지만, 요소가 추가됨에 따라 필요한 경우 내부적으로 크기가 자동으로 늘어난다. (cf 배열)
- 인덱스 기반의 접근: List<T>의 요소에는 0부터 시작하는 인덱스를 통해 접근할 수 있다. (IList<T> 구현)
- 다양한 메서드와 속성:
- Add: 요소를 리스트의 끝에 추가
- Insert: 지정된 인덱스에 요소를 삽입
- Remove 및 RemoveAt: 요소를 제거
- Count: 리스트에 있는 요소의 수를 반환
- Clear: 모든 요소를 제거
- Contains: 리스트가 특정 요소를 포함하고 있는지 확인 (bool 반환)
- IndexOf: 특정 요소의 인덱스를 반환
- 반복자 지원: List<T>는 foreach 루프를 사용하여 요소를 반복적으로 순회할 수 있다.
- IEnumerable 구현 : LINQ (Language Integrated Query)의 대부분의 쿼리 연산자는 IEnumerable<T> 타입을 반환한다. 그렇기 때문에 LINQ 쿼리의 결과는 foreach를 사용하여 순회할 수 있으며, 여러가지 IEnumerable<T>를 구현하는 다른 컬렉션 타입들로 쉽게 변환할 수 있다. (ToList(), ToArray() 등)
- 컬렉션 초기화: List<T>는 컬렉션 초기화(컬렉션을 생성하면서 동시에 그 안에 항목들을 추가하는 간결한 방법)를 사용하여 초기 요소를 설정할 수 있다.
- csharpCopy code List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
- 타입 안정성: 제네릭을 사용하기 때문에, List<T>는 타입 안전성을 제공한다. (ex. List<int>에 문자열을 추가하려고 시도하면 컴파일 오류가 발생)
- 내부 구현: List<T>의 내부에는 배열이 사용된다. 리스트가 가득 차면, 새로운, 더 큰 배열이 생성되고 기존의 데이터가 그 배열로 복사된다. → 동적으로 크기 확장
- List<T>가 처음 생성될 때 일정한 초기 용량(capacity)의 배열이 할당된다. 용량은 명시적으로 지정할 수도 있고, 지정하지 않을 경우에는 기본 용량이 사용된다.
- 항목을 리스트에 추가할 때 현재 배열의 용량이 충분하다면 그 배열 내의 다음 위치에 항목이 저장된다.
- 만약 항목을 추가하려 할 때 현재 배열의 용량이 부족하다면, 리스트는 새로운, 보통은 현재 용량의 2배 크기인 배열을 할당한다. 그 다음 현재 배열의 내용을 새로운 배열로 복사한 후, 현재 배열을 새로운 배열로 교체한다. 이후 새 항목은 새 배열에 추가된다.
- 항목을 삭제하더라도 배열의 용량이 자동으로 줄어들지는 않는다. 그렇기 때문에 큰 데이터를 임시로 저장했다가 모두 삭제한 경우, 실제 리스트의 크기(Count)는 0이지만 내부 배열의 용량(Capacity)는 여전히 큰 값일 수 있다. 이 경우 TrimExcess() 메서드를 호출하여 불필요한 용량을 해제할 수 있다.
활용 (어떤 경우에 쓰면 효율적?)
- 동적 크기: 항목의 수가 런타임에 결정되는 경우에 유용
- 빠른 임의 접근: 인덱스를 사용하여 빠르게 항목에 액세스할 수 있다.
- 순차적인 데이터 추가: Add 메서드를 사용한 항목 추가가 빠르다.4
사용할 때 주의사항
- 중간 삽입/삭제: List<T>의 중간에 항목을 삽입하거나 삭제하는 것은 상대적으로 느릴 수 있다. 이는 리스트의 내부 배열에서 항목을 이동해야 하기 때문. (이러한 연산이 빈번하게 필요한 경우 LinkedList<T>와 같은 다른 구조를 고려할 수 있다.)
- 크기 확장: List<T>의 내부용량이 가득 차면 자동으로 새로운, 더 큰 배열이 생성되고 기존 항목이 그곳으로 복사된. 이로 인해 크기 확장 연산은 상대적으로 시간이 많이 걸릴 수 있다. 크기 확장 연산을 최소화하려면 초기 List<T> 생성 시에 충분한 용량을 설정하는 것이 좋다.
- 동시성: List<T>는 동시에 여러 스레드에서 액세스될 경우 안정성을 보장하지 않는다. (동시에 여러 스레드에서 List<T>에 액세스해야 하는 경우에는 동기화 메커니즘이 필요)
🎱 관련 공부
제네릭과 타입 안정성
제네릭 타입 (Generics)
제네릭은 데이터 타입을 일반화하여 코드를 재사용하기 쉽게 만드는 프로그래밍 기법이다. 제네릭을 사용하면 하나의 클래스나 메서드를 다양한 데이터 타입에 대해 작동하도록 설계할 수 있다.
예를 들어, C#에서 리스트를 생성할 때:
csharpCopy code
List<int> intList = new List<int>();
List<string> stringList = new List<string>();
여기서 List<T>는 제네릭 타입이다. T는 어떤 데이터 타입이든 될 수 있으며, 이를 통해 intList는 정수들의 리스트가 되고, stringList는 문자열들의 리스트가 된다.
타입 안정성 (Type Safety)
타입 안정성은 프로그램에서 데이터 타입의 일관성을 보장하는 특성을 의미한다. 타입 안전한 코드에서는 예상하지 않은 데이터 타입의 값이 특정 변수에 할당되거나 반환되는 것을 방지한다.
타입 안정성은 버그를 미리 방지하고, 컴파일러가 잘못된 타입의 사용을 감지할 수 있게 해주어, 런타임 오류의 위험을 줄인다.
제네릭과 타입 안정성의 연관성
제네릭은 타입 안정성을 강화하는 데 크게 기여한다.
- 타입 안전한 코드 재사용: 제네릭을 사용하면 여러 타입에 대해 작동하는 코드를 작성할 수 있지만, 그 코드는 여전히 각 타입에 대해 안전하게 작동한다.
- 컴파일 시간의 타입 검사: 제네릭을 사용하면 컴파일러는 잘못된 타입의 사용을 미리 감지할 수 있다. 이로 인해 런타임에 발생할 수 있는 타입 관련 오류의 위험이 줄어든다.
- 예를 들어, 제네릭을 사용하지 않는 리스트에서 잘못된 타입의 객체를 추가하는 경우, 이는 런타임에 오류를 발생시킬 수 있다. 그러나 제네릭 리스트에서는 컴파일 시점에 타입의 불일치를 감지할 수 있으므로 이러한 오류를 미리 방지할 수 있다.
- 코드 재사용성: 같은 로직을 가진 여러 타입의 함수나 클래스를 별도로 작성할 필요가 없다. 제네릭을 사용하면, 다양한 타입에 대해 동일한 로직을 갖는 함수나 클래스를 한 번만 작성하면 된다. 예를 들어, int 리스트와 string 리스트에 대해 아이템을 추가하는 메서드를 별도로 작성할 필요 없이, 제네릭 리스트의 Add 메서드를 사용할 수 있다.
박싱과 언박싱
- 박싱: 값 타입을 Object 타입 또는 해당 값 타입의 인터페이스로 변환할 때 박싱이 발생한다. 이 때 값 타입의 값이 힙에 할당된 새로운 객체로 복사되며, 그 객체의 주소가 참조 변수에 저장된다. 복사되는 과정에서 해당 값 타입의 메타데이터(타입 정보 등)도 함께 복사된다.
- 언박싱: 박싱된 객체를 다시 원래의 값 타입으로 변환할 때 언박싱이 발생한다. 이 때 해당 객체의 메타데이터를 사용하여 원래의 값 타입을 확인하고, 객체의 값이 스택에 있는 원래의 값 타입 변수로 복사된다.
(Object는 .NET에서 모든 타입의 기본 클래스)
비제네릭 데이터 구조는 내부적으로 Object 타입의 배열을 사용하여 아이템들을 저장한다. 따라서, 값을 **ArrayList**에 저장할 때 값 타입 (예: int, doublestruct 등)은 객체 타입으로 박싱되어 저장된다.
박싱과 언박싱의 단점:
- 성능 오버헤드: 박싱과 언박싱 과정은 추가적인 메모리 할당과 가비지 컬렉션에 따른 성능 오버헤드를 초래한다.
- 타입 안정성: 런타임에 언박싱을 수행할 때, 잘못된 타입 변환이 발생할 수 있어 예외를 유발할 수 있다.
스택과 힙
(데이터 형식과 연관해서 알아본 것)
프로그램의 실행 중에 데이터를 저장하는 두 가지 주요 메모리 영역
스택(Stack):
- 정의: 스택은 LIFO(Last-In, First-Out / 후입선출) 구조로 동작하는 메모리 영역
- 데이터 저장: 로컬 변수, 매개변수, 반환 주소 등의 임시 데이터가 저장된다.
- 메모리 할당 및 반환: 스택 포인터가 이동함에 따라 자동으로 메모리가 할당되고 반환된다. 함수나 메서드가 호출될 때 해당 메서드의 로컬 변수와 관련 정보는 스택에 푸시되며, 메서드가 종료될 때 자동으로 팝되어 메모리가 반환된다.
- 효율:자동 메모리 관리(함수 호출 및 종료 시 데이터 관리. 후입선출 원칙)와 빠른 접근 속도를 제공
- 크기: 일반적으로 스택의 크기는 제한적이다. (스택 오버플로우는 스택의 크기를 초과하여 데이터를 할당하려고 할 때 발생하는 오류)
힙(Heap):
- 정의: 힙은 프로그램의 동적 메모리 할당을 위한 영역 (동적 - 런타임 중)
- 데이터 저장: 동적으로 할당된 객체, 배열 등의 데이터가 저장된다.
- 메모리 할당 및 반환: 프로그래머나 가비지 컬렉터(GC)가 명시적으로 메모리를 할당하고 반환한다. .NET, Java와 같은 언어에서는 가비지 컬렉터가 더 이상 참조되지 않는 객체를 자동으로 힙에서 정리
- 효율: 스택에 비해 메모리 할당과 반환은 느리며, 메모리 파편화의 위험이 있다.
- 여러 개의 객체가 메모리에 할당되고 해제될 때, 사용되지 않는 메모리 공간 (즉, "홀")이 여기저기에 흩어져 있게 된다. 이렇게 되면 충분한 총 메모리 공간이 있더라도, 연속적인 메모리 공간이 부족하여 새로운 객체를 할당할 수 없는 상황이 발생
- 실제로 필요한 것보다 큰 블록을 할당받아 그 안에서 일부분만 사용하는 경우
- 크기: 힙의 크기는 일반적으로 컴퓨터의 사용 가능한 RAM에 의해 결정된다.
데이터 타입과 저장
- 값 타입:
- 값타입은 메서드나 함수가 호출될 때 생성되고, 해당 메서드나 함수가 종료될 때 소멸된다. 그리고 스택은 후입선출 특성을 갖는 데이터의 생명주기를 효율적으로 관리할 수 있다.
- 값 타입은 일반적으로 크기가 고정되어 있다. 스택은 고정된 크기의 데이터를 효율적으로 저장하고 관리할 수 있다.
- 참조 타입:
- 동적으로 할당될 수 있으며, 그 생명주기가 명확하게 정의되어 있지 않다. 이러한 유형의 데이터는 런타임 중 언제든지 생성되고 삭제될 수 있다. 힙은 이러한 동적인 메모리 관리에 적합
- 동적으로 할당되므로 크기가 가변적일 수 있다. 힙은 이러한 가변적인 크기의 데이터를 관리하는 데 적합하다.
- 메모리의 데이터에 대한 참조(주소)가 저장된다. 이 참조가 다른 변수로 복사되면 동일한 메모리 위치를 가리키는 참조가 복사된다. 따라서 동적으로 할당된 객체를 여러 변수가 참조할 수 있다.
List<T> 클래스 (System.Collections.Generic)
List<T> 클래스 (System.Collections.Generic)
인덱스로 액세스할 수 있는 강력한 형식의 개체 목록을 나타냅니다. 목록의 검색, 정렬 및 조작에 사용할 수 있는 메서드를 제공합니다.
learn.microsoft.com
'👾 내일배움캠프 > 🎮 TIL & WIL' 카테고리의 다른 글
내일배움캠프 38일차 TIL - Tilemap 활용하기 (0) | 2023.10.22 |
---|---|
내일배움캠프 WIL 8주차 - 팀 프로젝트 (1) | 2023.10.21 |
내일배움캠프 36일차 TIL - 알고리즘 풀다가 C# 공부하기 (DateTime) (1) | 2023.10.06 |
내일배움캠프 35일차 TIL - 적 생성 및 로직 (Unity 네비게이션 시스템) (0) | 2023.10.06 |
내일배움캠프 34일차 TIL - 아이템 및 인벤토리 (1) | 2023.10.06 |