컬렉션 프레임워크(+자료구조 비교)

컬렉션 프레임워크(+자료구조 비교)

  • 컬렉션 프레임워크

    • java.util 패키지

      • 3대 주요 인터페이스

        • List

          • 순서가 있는 데이터의 집합.

          • 순서가 있으므로 데이터를 구별할 수 있어 중복 허락

          • ArrayList, LinkedList

          • Vector는 컬렉션 프레임워크가 나오기 이전부터 존재하는 클래스라 잘 사용하지 않음

        • Set

          • 순서를 유지하지 않는 데이터의 집합.

          • 순서가 없으므로 데이터를 구별할 수 없어 중복을 허락하지 않음

          • HashSet, TreeSet

        • Map

          • key와 value의 쌍으로 데이터를 관리하는 집합.

          • 순서가 없고 key의 중복 불가. value는 중복 가능

          • HashMap, TreeMap

          • Hashtable은 컬렉션 프레임워크가 나오기 이전부터 존재하는 클래스라 잘 사용하지 않음

    • Collection interface

      • 추가

        • add(E e)

        • addAll(Collection<? extends E> c)

      • 조회

        • contains(Object o)

        • containsAll(Collection<?> c)

        • equals()

        • isEmpty()

        • isterator()

        • size()

      • 삭제

        • clear()

        • removeAll(Collection<?> c)

        • retainAll(Collection<?> c)

      • 기타

        • toArray()
    • List의 주요 메서드

      • 추가

        • add(int index, E element)

        • addAll(int index, Collection>? exends E> c)

      • 조회

        • get(int index)

        • indexOf(Object o)

        • lastIndexOf(Object o)

        • listIterator()

      • 삭제

        • remove(int index)
      • 수정

        • set(int index, E element)
      • 기타

        • subList(int fromIndex, int toIndex)
    • Map의 주요 메서드

      • 추가

        • put(K Key, V Value)

        • putAll(Map<? extends K, ? extends V> m)

      • 조회

        • containsKey(Object key)

        • containsValue(Object value)

        • entrySet()

        • keySet()

        • get(Object key)

        • values()

        • size()

        • isEmpty()

      • 삭제

        • clear()

        • remove(Object key)

      • 수정

        • put(K key, V value)

        • putAll(Map<? extends K, ? extends V> m)

    • 자료 구조 비교

      • ArrayList vs LinkedList

        time

      • HashSet vs TreeSet

        HashSet vs TreeSet

      • LinkedHashMap, LinkedHashSet

        • 데이터의 저장과 순서를 기억하는 경우 사용한다

        • HashMap, HashSet과 더불어 Doubly-Linked-List로 데이터 삽입 순서를 기록한다

      • HashMap vs HashSet

        HashSet vs HashMap in Java - DataFlair

        • 속도 차이가 나는 이유

          • 상대적으로 Map의 Key 값의 해시 코드 변환이 Set의 객체의 해시 코드 계산보다 빠르다

          • HashSet은 HashMap을 기반으로 구현되어 있다

      • HashMap vs HashTable

        • HashMap

          • 보조해시를 사용하기 때문에 해시 충돌이 덜 발생할 수 있어 상대적으로 성능상 이점이 있다

          • 동기화를 지원하지 않으며 thread-safe 하지 않다

          • IDE의 구현부를 보면, 아래 Hashtable과는 다르게 synchronized 키워드가 없음

            // get method
            public V get(Object key) {
              Node<K,V> e;
              return (e = getNode(hash(key), key)) == null ? null : e.value;
            }
            
            // put method
            public V put(K key, V value) {
              return putVal(hash(key), key, value, false, true);
            }
            
        • HashTable

          • 다른 스레드가 block되고 unblock 되는 대기 시간을 기다려야돼서 상대적으로 느리다

          • 동기화를 지원하며 thread-safe하다

          • IDE를 이용해 구현부를 보면 synchronized 키워드가 붙어있다

            // get method
            public synchronized V get(Object key) {
              // ... 중략 ...
            }
            
            // put method
            public synchronized V put(K key, V value) {
              // ... 중략 ...
            }
            
          • Null을 허용하지 않는다

      • ConcurrentHashMap

        • Hashtable 클래스의 단점을 보완하면서 multi-thread 환경에서 사용할 수 있도록 나온 클래스이다

          public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
            implements ConcurrentMap<K,V>, Serializable {
          
            public V get(Object key) {}
          
            public boolean containsKey(Object key) { }
          
            public V put(K key, V value) {
                return putVal(key, value, false);
            }
          
            final V putVal(K key, V value, boolean onlyIfAbsent) {
                if (key == null || value == null) throw new NullPointerException();
                int hash = spread(key.hashCode());
                int binCount = 0;
                for (Node<K,V>[] tab = table;;) {
                    Node<K,V> f; int n, i, fh;
                    if (tab == null || (n = tab.length) == 0)
                        tab = initTable();
                    else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                        if (casTabAt(tab, i, null,
                                     new Node<K,V>(hash, key, value, null)))
                            break;                   // no lock when adding to empty bin
                    }
                    else if ((fh = f.hash) == MOVED)
                        tab = helpTransfer(tab, f);
                    else {
                        V oldVal = null;
                        synchronized (f) {
                            if (tabAt(tab, i) == f) {
                                if (fh >= 0) {
                                    binCount = 1;
                                    for (Node<K,V> e = f;; ++binCount) {
                                        K ek;
                                        if (e.hash == hash &&
                                            ((ek = e.key) == key ||
                                             (ek != null && key.equals(ek)))) {
                                            oldVal = e.val;
                                            if (!onlyIfAbsent)
                                                e.val = value;
                                            break;
                                        }
                                        Node<K,V> pred = e;
                                        if ((e = e.next) == null) {
                                            pred.next = new Node<K,V>(hash, key,
                                                                      value, null);
                                            break;
                                        }
                                    }
                                }
                                else if (f instanceof TreeBin) {
                                    Node<K,V> p;
                                    binCount = 2;
                                    if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                                   value)) != null) {
                                        oldVal = p.val;
                                        if (!onlyIfAbsent)
                                            p.val = value;
                                    }
                                }
                            }
                        }
                        if (binCount != 0) {
                            if (binCount >= TREEIFY_THRESHOLD)
                                treeifyBin(tab, i);
                            if (oldVal != null)
                                return oldVal;
                            break;
                        }
                    }
                }
                addCount(1L, binCount);
                return null;
            }
          }
          
        • 위의 코드는 ConcurrentHashMap 클래스의 일부 API 입니다. ConcuurentHashMap에는 Hashtable 과는 다르게 synchronized 키워드가 메소드 전체에 붙어 있지 않습니다.

        • get() 메소드에는 아예 synchronized가 존재하지 않고, put() 메소드에는 중간에 synchronized 키워드가 존재하는 것을 볼 수 있습니다.

        • 이것을 좀 더 정리해보면 ConcurrentHashMap은 읽기 작업에는 여러 쓰레드가 동시에 읽을 수 있지만, 쓰기 작업에는 특정 세그먼트 or 버킷에 대한 Lock을 사용한다는 것입니다.

  • 참고 자료