Collections
Collections Framework
JDK 1.2 전에는 다수의 데이터를 저장할 수 있는 클래스들을 서로 다른 방식으로 처리해야 했으나, JDK1.2 부터 컬렉션 클래스로 표준화된 방식으로 다룰 수 있게 체계화되었다.
List 와 Set을 구현한 컬렉션 클래스들은 공통 부분이 많아서, Collection 인터페이스로 정의할 수 있었지만 Map 인터페이스는 Key-Value 형태로 컬렉션을 다루기 때문에 같은 상속 계층도에 포함되지 못한다.
Collection 인터페이스를 보면 제네릭을 사용한 것을 볼수 있다. 제네릭은 Reference타입만을 할당받을 수 있기 떄문에 Collection의 구현클래스를 선언할때 Primitive타입은 사용이 불가하다!
public interface Collection<E> extends Iterable<E>List (opens in a new tab)
- 데이터의 저장 순서가 유지되고, 중복을 허용함
- 구현 클래스:
AraryList,LinkedListVector,Stack등은 컬렉션 프레임워크가 만들어지기 이전부터 존재하던 것이기 때문에 명명법을 따르지 않음.Vector와ArrayList는 내부 구조가 거의 동일하고,Vector는Thread-safe하기 때문에 멀티 스레드 환경에서 안전하게 객체를 추가, 삭제할 수 있지만,ArrayList는 그렇지 않다.Vector과 같은 컬렉션들은 기존 코드와의 호환성을 위해 남겨두었고,ArrayList를 사용하는 것이 좋다.
ArrayList (opens in a new tab)
- Object 배열을 사용해서 데이터를 순차적으로 저장
Object배열은Thread-safe하지 않음
- 배열 크기를 변경할 수 없음
- 크기를 변경해야 하는 경우 새로운 배열을 생성 후 데이터를 복사해야 함
- 크기 변경을 피하기 위해 충분히 큰 배열을 생성하면 메모리가 낭비됨
- 비순차적인 데이터의 추가, 삭제에 시간이 많이 걸림
- 배열의 크기를 늘릴 때, 현재 배열의 크기의 1.5배 크기(
oldCapacity >> 1)로 늘림
LinkedList (opens in a new tab)
- 이중 연결 리스트로 구현되어 있다. 배열과 달리 불연속적으로 존재하는 데이터를 연결한다
- 단 한 번의 참조 변경만으로 데이터 삭제 가능
- 인덱스로
O(1)참조가 가능한 배열에 비해 데이터 접근성이 나쁨
ArraysList vs LinkedList 성능 비교
| 연산 유형 | ArrayList | LinkedList | 설명 |
|---|---|---|---|
| 조회 (get(i)) | 🚀 O(1) | 🐢 O(N) | ArrayList는 인덱스(i)로 바로 접근 가능, LinkedList는 처음부터 순차 탐색 필요. |
| 삽입 (add(i, e)) | 🐢 O(N) (중간) | 🐢 O(N) (중간) | 중간 삽입의 경우, ArrayList는 요소 이동, LinkedList는 노드 탐색 후 포인터 변경 필요. |
| 삽입 (add(e)) | 🚀 O(1) (끝) | 🚀 O(1) (끝) | 끝에 추가하는 경우, ArrayList는 공간이 부족하면 배열 확장(O(N)). |
| 삭제 (remove(i)) | 🐢 O(N) | 🐢 O(N) | 중간 삭제의 경우, ArrayList는 요소 이동, LinkedList는 노드 탐색 후 포인터 변경 필요. |
| 삭제 (remove(e)) | 🐢 O(N) | 🐢 O(N) | 특정 요소 삭제 시, ArrayList는 인덱스를 먼저 찾고, LinkedList도 노드를 탐색해야 함. |
| 검색 (indexOf(e)) | 🐢 O(N) | 🐢 O(N) | 특정 요소의 인덱스를 찾기 위해 전체 탐색 필요. |
| 메모리 사용 | 📦 적음 | 📦📦 많음 | ArrayList는 인덱스 기반, LinkedList는 각 노드에 포인터(주소) 저장. |
Set (opens in a new tab)
- 순서를 유지하지 않는 데이터의 집합, 데이터의 중복을 허용하지 않음
- 구현 클래스:
HashSet,TreeSet
HashSet (opens in a new tab)
- 저장 순서를 유지 하지 않음
- 저장 순서를 유지하고자 한다면
LinkedHashSet을 사용하면 됨.
- 저장 순서를 유지하고자 한다면
- 순서가 없으니 정렬을 할 수 없음.
- 정렬을 하고자 한다면
Set의 모든 요소를List에 저장해서 정렬하거나TreeSet을 사용하면 됨.
- 정렬을 하고자 한다면
- 객체를 저장하기(
boolean add(Object o)) 전에 기존에 같은 객체가 있는지 확인- 같은 객체가 없으면 저장하고, 있으면 저장하지 않는다
- 같은 객체를 확인하는 것은
hashCode()메서드를 호출해서 해시코드를 얻어냄.- 같은 해시코드를 가진 객체가 있다면
equals()메서드로 두 객체를 비교해서true가 나오면 같은 객체로 판단하고 저장하지 않음. - 제대로 저장하려면
equals()와hashCode()를 목적에 맞게 재정의해야함.
- 같은 해시코드를 가진 객체가 있다면
- 내부적으로
HashMap을 사용함.HashSet은Map키에 해당하는 부분에 객체를 저장하고 값에는 더미 객체를 사용함.
TreeSet (opens in a new tab)
Binary Search Tree로 구현되어 있음.- 범위 검색과 정렬에 유리한 집합
HashSet보다 데이터 추가, 삭제에 시간이 더 걸림- 내부적으로
TreeMap을 사용함.Treㄷset은Map키에 해당하는 부분에 객체를 저장하고 값에는 더미 객체를 사용함. - BST 자료구조에 저장되기 때문에 객체 간의 비교가 가능해야 함
- 저장되는 객체가
Comparable나Comparator인터페이스를 구현하고 있어야 함 - 혹은 생성자에
Comparator객체를 넘겨줘야 함 (TreeSet(Comparator comparator))
- 저장되는 객체가
TreeSet API
subSet(Object fromElement, Object toElement): 범위 검색의 결과를 반환headSet(Object toElement): 지정된 객체보다 작은 객체들을 반환tailSet(Object fromElement): 지정된 객체보다 큰 객체들을 반환- 그 외 기타 API 는 공식문서 참고
Java List vs Set
| List | Set | |
|---|---|---|
| 구현체 | ArrayList, LinkedList | HashSet, LinkedHashSet, TreeSet |
| 중복 데이터 저장 | 가능 | 불가능 |
| 데이터 조회 속도 | 상대적으로 느림 | 상대적으로 매우 빠름 |
| 순서 존재 | 존재함 | 구현체에 따라 다름 |
Map (opens in a new tab)
- key-value 형식으로 데이터를 저장하는 자료구조
key는 유니크 해야함.value는 중복 가능HashTable과HashMap의 관계는Vector와ArrayList의 관계와 같다.HashTable은Thread-safe하지만HashMap은 그렇지 않다.
HashMap (opens in a new tab)
- 해시 테이블을 사용한다
- 해시를 사용하기 때문에 보통 모든 데이터를 상수 시간에 접근한다
- 순서를 보장하지 않는다
- 순서를 유지하려면,
LinkedHashMap을 사용하면 된다
- 순서를 유지하려면,
- 해시 충돌 시 체이닝 방식 (opens in a new tab)으로 해결한다
TreeMap (opens in a new tab)
Binary Search Tree로 구현되어 있음.- 범위 검색과 정렬에 유리한 집합
HashMap보다 데이터 추가, 삭제에 시간이 더 걸림
유용한 메서드
Collections.copy(List dest, List src): src의 내용을 dest로 복사Collections.sort(List list): list를 오름차순으로 정렬Collections.sort(List list, Comparator c): list를 c의 규칙으로 정렬Collections.binarySearch(List list, Object key): 이진 검색으로 list에서 key를 찾아서 인덱스를 반환Collections.reverse(List list): list의 내용을 반대로 정렬Collections.shuffle(List list): list의 내용을 뒤섞음