Collection Framework는 데이터를 효율적으로 저장하고 관리하기 위한 자바의 핵심 라이브러리다. 각 구현체는 내부 자료구조와 동작 방식에 따라 성능 특성이 다르므로, 상황에 맞는 컬렉션을 선택하는 것이 중요하다.
순서가 있고 중복을 허용하는 컬렉션이다.
Object[] elementData)을 사용한다.public E get(int index) {
return elementData[index]; // O(1)
}
배열의 인덱스로 직접 접근하므로 조회 시간은 O(1)이다.
public void add(int index, E element) {
System.arraycopy(elementData, index,
elementData, index + 1,
size - index);
elementData[index] = element;
}
System.arraycopy()를 사용하여 네이티브 레벨에서 메모리를 복사한다.private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5배 증가
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
}
public E get(int index) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x.item;
}
void linkBefore(E e, Node<E> succ) {
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
}
중복을 허용하지 않는 컬렉션이다.
private transient HashMap<E,Object> map;
public boolean contains(Object o) {
return map.containsKey(o);
}
hashCode()로 버킷을 찾는다.equals()로 동일성을 확인한다.void resize() {
Node<K,V>[] oldTab = table;
int oldCap = oldTab.length;
int newCap = oldCap << 1; // 2배 증가
// rehashing...
}
private transient LinkedHashMap<E,Object> map;
private transient NavigableMap<E,Object> m;
public boolean contains(Object o) {
return m.containsKey(o);
}
Key-Value 쌍으로 데이터를 저장하는 컬렉션이다.
transient Node<K,V>[] table;
static class Node<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
public V get(Object key) {
Node<K,V> e = getNode(hash(key), key);
return e == null ? null : e.value;
}
put() 시 해시값으로 버킷을 찾아 저장한다.final Node<K,V>[] resize() {
int oldCap = table.length;
int newCap = oldCap << 1;
// ... rehashing
}
transient volatile Node<K,V>[] table;
static class Node<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
}
public V get(Object key) {
Node<K,V>[] tab = table;
Node<K,V> e = tabAt(tab, (n - 1) & hash);
// ... search
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
for (Node<K,V>[] tab = table;;) {
if (tabAt(tab, i) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
break;
} else {
synchronized (f) {
// ... insert
}
}
}
}
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
}
private transient Entry<K,V> root;
static final class Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
}
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
| 컬렉션 | 조회 | 삽입/삭제(끝) | 삽입/삭제(중간) | 정렬 | 동기화 |
|---|---|---|---|---|---|
| ArrayList | O(1) | O(1)* | O(n) | X | X |
| LinkedList | O(n) | O(1) | O(n) | X | X |
| HashSet | O(1) | O(1) | - | X | X |
| LinkedHashSet | O(1) | O(1) | - | 순서유지 | X |
| TreeSet | O(log n) | O(log n) | - | O | X |
| HashMap | O(1) | O(1) | - | X | X |
| ConcurrentHashMap | O(1) | O(1) | - | X | O |
| LinkedHashMap | O(1) | O(1) | - | 순서유지 | X |
| TreeMap | O(log n) | O(log n) | - | O | X |
*amortized(분할 상환) 시간 복잡도
컬렉션을 선택할 때는 데이터의 특성과 필요한 연산을 고려해야 한다. 조회가 빈번하면 ArrayList나 HashMap을, 삽입/삭제가 많으면 LinkedList를, 정렬이 필요하면 TreeSet이나 TreeMap을, 멀티스레드 환경이면 ConcurrentHashMap을 사용하는 것이 적절하다.