15_컬렉션 자료구조

Tasker_Jang·2024년 3월 25일
0

15_컬렉션 자료구조

1. 컬렉션 프레임워크

컬렉션 프레임워크는 Java의 java.util 패키지에 포함된 인터페이스와 클래스들의 모음을 가리킵니다. 이 프레임워크는 객체들을 효율적으로 추가, 삭제, 검색할 수 있도록 다양한 자료 구조를 제공합니다. 이를 통해 프로그래머들은 데이터를 보다 쉽게 관리하고 다룰 수 있습니다. 컬렉션 프레임워크의 핵심 인터페이스에는 List, Set, Map 등이 포함되어 있습니다. 이러한 인터페이스를 구현한 클래스들은 ArrayList, LinkedList, HashSet, HashMap 등이 있습니다. 이러한 클래스들은 다양한 상황에 따라 선택하여 사용할 수 있으며, 프로그래머가 필요에 따라 데이터를 저장하고 처리할 수 있도록 도와줍니다.

2. List

List는 Java에서 순서가 있는 데이터의 집합을 관리하는 인터페이스입니다. 이를 구현하는 클래스들은 객체를 인덱스로 관리하며, 이를 통해 객체를 검색하고 삭제할 수 있습니다. List는 중복된 요소를 허용하며, 순서를 유지합니다. 이는 객체의 번지를 저장함으로써 가능합니다.

여러 List 구현 클래스가 있지만, 가장 널리 사용되는 것은 ArrayList, Vector, LinkedList입니다.

  1. ArrayList: 내부적으로 배열을 사용하여 요소를 저장합니다. 배열의 크기를 유동적으로 조절할 수 있어 동적인 크기 조정이 가능합니다. 빠른 접근 속도를 제공하지만 중간에 요소를 삽입 또는 삭제할 때는 성능이 저하될 수 있습니다.

  2. Vector: ArrayList와 유사하지만 스레드에 안전한 동기화 메커니즘이 구현되어 있습니다. 따라서 멀티스레드 환경에서 안전하게 사용할 수 있습니다. 하지만 동기화 오버헤드로 인해 성능이 느릴 수 있습니다.

  3. LinkedList: 이중 연결 리스트 구조를 사용하여 요소를 저장합니다. 요소의 삽입과 삭제가 빈번한 경우에 유용하며, 이전 및 다음 요소에 대한 참조를 유지하기 때문에 요소의 추가 및 삭제가 효율적입니다. 그러나 인덱스로 직접 접근하는 데에는 성능이 떨어질 수 있습니다.

이러한 List 구현 클래스들은 다양한 상황에 맞게 선택하여 사용할 수 있으며, 개발자는 데이터를 효율적으로 관리하고 다루기 위해 이를 활용할 수 있습니다.

3. ArrayList

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        // ArrayList 생성
        ArrayList<String> arrayList = new ArrayList<>();

        // 객체 추가
        arrayList.add("Apple"); // 인덱스 0
        arrayList.add("Banana"); // 인덱스 1
        arrayList.add("Orange"); // 인덱스 2

        System.out.println("ArrayList after adding elements: " + arrayList);

        // 특정 인덱스의 객체 제거
        arrayList.remove(1); // Banana 제거

        System.out.println("ArrayList after removing element at index 1: " + arrayList);
    }
}
ArrayList after adding elements: [Apple, Banana, Orange]
ArrayList after removing element at index 1: [Apple, Orange]

ArrayList에 객체를 추가하면 인덱스가 순차적으로 부여됩니다. Banana를 제거하면 해당 인덱스 이후의 모든 요소들이 한 칸씩 앞으로 당겨지는 것을 확인할 수 있습니다.

4. Vector

Vector 클래스는 내부적으로 동기화된 메서드로 구성되어 있기 때문에 멀티 스레드 환경에서 안전하게 사용할 수 있습니다. 이는 Vector 클래스의 모든 메서드가 동기화되어 있어 여러 스레드가 동시에 접근하더라도 데이터의 일관성을 보장합니다.

이러한 동기화된 접근 방식으로 인해 여러 스레드가 동시에 Vector 객체의 메서드를 실행할 때 경합(경쟁 상태)이 발생하지 않습니다. 따라서 Vector는 멀티 스레드 환경에서 안전하게 사용할 수 있는 대표적인 컬렉션 클래스입니다.

import java.util.Vector;

public class VectorExample {
    public static void main(String[] args) {
        // Vector 객체 생성
        Vector<Integer> vector = new Vector<>();

        // 스레드 배열 생성
        Thread[] threads = new Thread[5];

        // 각 스레드에서 Vector에 요소 추가하는 작업 수행
        for (int i = 0; i < threads.length; i++) {
            final int index = i;
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 10; j++) {
                    // 동기화된 메서드인 add()를 호출하여 Vector에 요소 추가
                    vector.add(index * 10 + j);
                    System.out.println("Added: " + (index * 10 + j));
                }
            });
            threads[i].start(); // 스레드 시작
        }

        // 모든 스레드가 종료될 때까지 대기
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // Vector 출력
        System.out.println("Vector contents: " + vector);
    }
}

5. Linked List

LinkedList는 인접한 객체를 체인처럼 연결하여 관리하는 자료 구조입니다. 각 객체는 이전 객체와 다음 객체에 대한 참조(링크)를 가지고 있어 순차적으로 연결되어 있습니다.

LinkedList는 객체를 삽입하거나 삭제할 때에는 해당 위치의 앞뒤 링크만 변경하면 되므로, ArrayList보다 빠른 성능을 발휘할 수 있습니다. 특히, 중간에 객체를 삽입하거나 삭제하는 경우에 유용합니다. 이는 ArrayList가 배열을 사용하여 요소를 저장하기 때문에 삽입 또는 삭제 시에는 배열의 재배치가 필요하기 때문입니다.

따라서 객체의 삽입과 삭제가 빈번하게 발생하는 상황에서는 LinkedList가 ArrayList보다 성능적으로 더 좋은 선택일 수 있습니다. 하지만 인덱스를 통한 임의의 접근이 많이 필요한 경우에는 LinkedList의 성능이 떨어질 수 있습니다. 이는 LinkedList에서는 각 요소에 직접적으로 접근할 때마다 링크를 타고 가야하기 때문입니다.


import java.util.LinkedList;

public class LinkedListExample {
    public static void main(String[] args) {
        // LinkedList 객체 생성
        LinkedList<String> linkedList = new LinkedList<>();

        // 객체 삽입
        linkedList.add("Apple");
        linkedList.add("Banana");
        linkedList.add("Orange");

        System.out.println("LinkedList after adding elements: " + linkedList);

        // 특정 위치에 객체 삽입
        linkedList.add(1, "Grape");

        System.out.println("LinkedList after adding 'Grape' at index 1: " + linkedList);

        // 객체 삭제
        linkedList.remove("Banana");

        System.out.println("LinkedList after removing 'Banana': " + linkedList);
    }
}

6. Set

Set은 Java에서 컬렉션 프레임워크의 한 부분으로, 집합(Set)과 유사한 개념을 가지고 있습니다. Set 컬렉션은 다음과 같은 특징을 갖습니다:

  1. 저장 순서가 유지되지 않는다: Set은 요소들을 저장할 때 내부적으로 순서를 유지하지 않습니다. 따라서 저장된 순서를 보장하지 않습니다.

  2. 중복 저장이 불가능하다: Set은 동일한 요소를 중복해서 저장할 수 없습니다. 즉, Set은 중복된 요소를 허용하지 않습니다.

  3. 인덱스로 관리하지 않기 때문에 인덱스를 매개값으로 갖는 메소드가 없다: Set은 요소들을 인덱스로 관리하지 않습니다. 따라서 List와는 달리 인덱스를 매개값으로 받는 메서드를 제공하지 않습니다.

Set은 대표적으로 HashSet, TreeSet, LinkedHashSet 등의 구현 클래스를 가지고 있습니다. 각 구현 클래스는 내부적으로 요소를 저장하는 방식에 따라 다르며, 사용 목적에 따라 선택하여 사용할 수 있습니다.

import java.util.HashSet;
import java.util.Set;

public class SetExample {
    public static void main(String[] args) {
        // HashSet 객체 생성
        Set<String> set = new HashSet<>();

        // 요소 추가
        set.add("Apple");
        set.add("Banana");
        set.add("Apple"); // 중복된 요소를 추가하려고 하면 무시됨

        System.out.println("Set contents: " + set);
    }
}
Set contents: [Apple, Banana]

Set은 중복된 요소를 허용하지 않으며, 저장된 순서를 보장하지 않습니다.

7. Hashset

HashSet은 동등한 객체를 중복해서 저장하지 않는 특징을 갖습니다. 이는 HashSet이 내부적으로 해시 함수를 사용하여 요소들을 저장하고 관리하기 때문에 가능합니다.

HashSet은 객체를 저장할 때 객체의 hashCode() 메서드를 호출하여 해시 코드를 계산합니다. 그리고 동일한 해시 코드를 가진 객체가 이미 HashSet에 존재하는지를 검사합니다. 만약 동일한 해시 코드를 가진 객체가 존재한다면, equals() 메서드를 사용하여 두 객체가 동등한지 비교합니다. 만약 equals() 메서드가 true를 반환한다면 HashSet은 해당 객체를 중복 저장하지 않고 무시합니다.

따라서 HashSet에 객체를 추가하기 전에 hashCode()와 equals() 메서드를 적절하게 구현하여 동등성을 정의하는 것이 중요합니다. 이를 통해 HashSet이 객체의 중복을 올바르게 판단할 수 있게 됩니다. 동등한 객체를 중복해서 저장하지 않는 HashSet의 특성은 컬렉션의 유용한 기능 중 하나로, 많은 상황에서 중복 요소를 효율적으로 관리할 수 있도록 도와줍니다.

8. iterator()

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class SetIteratorExample {
    public static void main(String[] args) {
        // HashSet 객체 생성
        Set<String> set = new HashSet<>();

        // 요소 추가
        set.add("Apple");
        set.add("Banana");
        set.add("Orange");

        // Iterator를 사용하여 Set의 요소들을 순회하며 출력
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()) {
            String element = iterator.next();
            System.out.println("Element: " + element);
        }
    }
}

9. Map

Map은 키(key)와 값(value)으로 구성된 쌍(pair)을 저장하는 자료 구조입니다. 각각의 키는 유일해야 하지만, 값은 중복될 수 있습니다. 따라서 동일한 키로 값을 저장하면 기존의 값은 대체되어 새로운 값으로 대치됩니다.

Map 컬렉션은 다양한 구현체가 있으며, 대표적인 것으로는 HashMap, TreeMap, LinkedHashMap 등이 있습니다.

import java.util.HashMap;
import java.util.Map;

public class MapExample {
    public static void main(String[] args) {
        // HashMap 객체 생성
        Map<String, Integer> map = new HashMap<>();

        // 값 추가
        map.put("Apple", 100);
        map.put("Banana", 200);
        map.put("Orange", 150);

        // 중복된 키로 값을 저장하면 기존 값은 대치됨
        map.put("Apple", 120);

        // 값 가져오기
        System.out.println("Value for key 'Apple': " + map.get("Apple"));

        // Map 순회 및 출력
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }
    }
}

10. Map의 다양한 종류들

  1. HashMap: 해시맵은 가장 일반적으로 사용되는 Map 구현 클래스 중 하나입니다. 해시맵은 해시 테이블을 사용하여 키와 값을 저장하며, 키는 중복될 수 없습니다. 내부적으로 해시 함수를 사용하여 키를 해시코드로 매핑하고, 이를 통해 키와 값을 빠르게 검색할 수 있습니다.

  2. Hashtable: 해시테이블은 HashMap과 비슷한 동작을 갖지만, 동기화된 메서드로 구성되어 있어 멀티 스레드 환경에서 안전하게 사용할 수 있습니다. 그러나 동기화된 메서드로 인해 성능이 떨어질 수 있습니다.

  3. LinkedHashMap: LinkedHashMap은 해시맵과 링크드 리스트의 결합체입니다. 이전에 추가된 순서대로 요소를 순회할 수 있으며, 내부적으로는 해시맵과 링크드 리스트를 사용하여 요소를 저장합니다. 따라서 순서가 중요한 경우에 유용합니다.

  4. Properties: Properties는 특별한 형태의 Map으로서, 주로 속성(property) 파일을 읽고 쓰는 데 사용됩니다. 키와 값이 각각 String 형식으로 제한되어 있으며, 주로 애플리케이션의 설정 정보를 저장하는 데 사용됩니다.

  5. TreeMap: 트리맵은 이진 검색 트리(Binary Search Tree)를 기반으로 키와 값을 저장합니다. 키는 정렬된 순서로 저장되어 있으며, 따라서 순서가 중요한 경우에 사용됩니다.

11. TreeSet과 TreeMap

TreeSet은 이진 검색 트리(Binary Search Tree)를 기반으로 한 Set 컬렉션입니다. 이진 검색 트리는 각 노드가 최대 두 개의 자식 노드를 가질 수 있는 구조를 갖습니다. 이진 검색 트리는 정렬된 상태를 유지하며, 검색, 삽입, 삭제 연산에 대해 평균적으로 O(log n)의 시간 복잡도를 가집니다.

TreeSet은 Set 인터페이스를 구현하므로 중복된 요소를 허용하지 않습니다. 또한 TreeSet은 이진 검색 트리의 특성을 이용하여 요소들을 정렬된 상태로 유지합니다. 따라서 TreeSet은 검색에 특화되어 있으며, 이진 검색 트리의 특성을 활용하여 빠르게 요소를 찾을 수 있습니다.

Set 인터페이스에는 검색과 관련된 메서드가 없습니다. 따라서 TreeSet에서는 추가적으로 검색을 위한 메서드가 제공됩니다. 예를 들어, TreeSet에서는 ceiling(), floor(), higher(), lower() 등의 메서드를 통해 요소를 검색할 수 있습니다. 이러한 메서드들은 이진 검색 트리의 특성을 활용하여 주어진 값에 가장 근접한 요소를 찾아줍니다.

TreeMap도 TreeSet과 마찬가지로 이진 검색 트리를 기반으로 하며, 검색에 특화된 기능을 제공합니다. TreeMap은 키-값 쌍을 저장하며, 이진 검색 트리의 특성을 활용하여 키를 정렬된 상태로 유지합니다. 따라서 TreeMap도 검색과 관련된 메서드를 추가로 제공합니다.

12. 스택과 큐

스택(Stack)은 Last-In-First-Out(LIFO)의 자료구조를 구현한 클래스이며, 큐(Queue)는 First-In-First-Out(FIFO)의 자료구조를 구현한 인터페이스입니다.

스택은 데이터를 넣을 때는 한 쪽 끝에서만 넣고, 데이터를 뺄 때는 그 반대쪽 끝에서만 뺄 수 있는 구조를 갖습니다. 따라서 가장 마지막에 추가된 요소가 가장 먼저 제거됩니다. 이러한 특성 때문에 스택은 후입선출(LIFO, Last-In-First-Out) 구조라고도 합니다. 스택은 주로 함수 호출이나 재귀 알고리즘 등에서 사용됩니다.

큐는 데이터를 넣을 때는 한 쪽 끝에서 넣고, 데이터를 뺄 때는 반대쪽 끝에서 뺄 수 있는 구조를 갖습니다. 따라서 가장 먼저 추가된 요소가 가장 먼저 제거됩니다. 이러한 특성 때문에 큐는 선입선출(FIFO, First-In-First-Out) 구조라고도 합니다. 큐는 주로 작업 대기열, 버퍼, 이벤트 처리 등에서 사용됩니다.

Java에서는 스택을 구현한 클래스로는 java.util.Stack이 있고, 큐를 구현한 인터페이스로는 java.util.Queue가 있습니다. Java에서는 Queue를 구현한 여러 클래스들도 제공되며, 가장 일반적인 구현체로는 java.util.LinkedList를 사용할 수 있습니다.

13. 동기화된 메서드

Java 컬렉션 프레임워크는 멀티스레드 환경에서 안전하게 사용할 수 있도록 동기화된 메서드를 제공합니다. 이러한 메서드들은 컬렉션의 일부 메서드를 동기화된 버전으로 래핑하여 여러 스레드가 안전하게 컬렉션을 사용할 수 있도록 합니다.

동기화된 메서드를 사용하려면 컬렉션 클래스의 이름 앞에 "synchronized"를 붙인 동기화된 버전의 메서드를 호출하면 됩니다. 예를 들어, ArrayList에 대한 동기화된 버전의 메서드는 synchronizedList() 메서드를 사용하여 생성할 수 있습니다.

여러 동기화된 메서드들이 제공되며, 일부 예시는 다음과 같습니다:

  1. synchronizedList(): ArrayList의 동기화된 버전인 List를 반환합니다.
  2. synchronizedSet(): HashSet의 동기화된 버전인 Set을 반환합니다.
  3. synchronizedMap(): HashMap의 동기화된 버전인 Map을 반환합니다.

동기화된 메서드를 사용하면 여러 스레드가 동시에 컬렉션에 접근하는 것을 막을 수 있으므로, 멀티스레드 환경에서 안전하게 컬렉션을 사용할 수 있습니다.

14. of() 와 copyOf()

Java 9부터는 수정할 수 없는 불변(immutable) 컬렉션을 만들기 위한 정적 메서드인 of()copyOf()가 추가되었습니다. 이러한 메서드들은 수정할 수 없는 컬렉션을 생성하며, 컬렉션의 내용을 변경하려는 시도는 예외를 발생시킵니다. 이를 통해 안전하게 컬렉션을 사용할 수 있으며, 멀티스레드 환경에서도 안전하게 공유할 수 있습니다.

  1. of() 메서드: 이 메서드는 주어진 요소들로부터 수정할 수 없는 컬렉션을 생성합니다. 이 메서드는 가변 인자(varargs)를 받아들이며, 최대 10개의 요소까지만 받을 수 있습니다. 예를 들어, List.of(element1, element2, ...)이나 Set.of(element1, element2, ...)과 같이 사용할 수 있습니다.

  2. copyOf() 메서드: 이 메서드는 다른 컬렉션의 요소들을 복사하여 수정할 수 없는 컬렉션을 생성합니다. 예를 들어, List.copyOf(collection)이나 Set.copyOf(collection)과 같이 사용할 수 있습니다.

import java.util.List;
import java.util.Set;

public class ImmutableCollectionExample {
    public static void main(String[] args) {
        // 수정할 수 없는 리스트 생성
        List<String> immutableList = List.of("Apple", "Banana", "Orange");

        // 수정할 수 없는 집합 생성
        Set<String> immutableSet = Set.of("Apple", "Banana", "Orange");

        // 기존 컬렉션으로부터 수정할 수 없는 리스트 생성
        List<String> originalList = new ArrayList<>();
        originalList.add("Apple");
        originalList.add("Banana");
        originalList.add("Orange");
        List<String> copiedList = List.copyOf(originalList);

        // 수정 시도 (예외 발생)
        try {
            immutableList.add("Grape"); // UnsupportedOperationException 발생
        } catch (UnsupportedOperationException e) {
            System.out.println("Immutable List: " + e.getMessage());
        }

        try {
            copiedList.add("Grape"); // UnsupportedOperationException 발생
        } catch (UnsupportedOperationException e) {
            System.out.println("Copied List: " + e.getMessage());
        }
    }
}
profile
터널을 지나고 있을 뿐, 길은 여전히 열려 있다.

0개의 댓글