본문 바로가기
자바/자바 자료구조

자바 - Map 이야기

by 신재은👩🏼‍💻 2024. 4. 26.

몇 분 전에 내 역작 '자바 - List - LinkedList 뽀개기'를 날려 먹어 기분이 심히 좋지 않으나

그래도 할 일은 해야 하기에 Map으로 바로 넘어간다.

 

1. 맵은 '키:값' 형태의 자료구조다.

각 키는 고유해야 한다.

각 키에는 하나의 값이 매핑되어야 한다.

우선은 이것만 기억하자.


2. 맵은 인터페이스다. 따라서 자바 안에는 이 인터페이스를 구현한 많은 클래스들이 있다.

각 클래스를 알고 각 클래스 안의 메서드를 다 알아야 코테를 잘 풀 거 아닌가?

Java의 주요 Map 구현체

  1. HashMap
    1. 순서를 보장하지 않는 맵으로, 키와 값을 해시 테이블을 사용하여 저장합니다. <- 보통은 얘로!!!
    2. 키의 순서에 의존하지 않는 애플리케이션에 적합하며, 일반적으로 가장 많이 사용됩니다.
    3. null 키와 null 값이 허용됩니다.
  2. TreeMap
    1. 자동으로 정렬된 순서로 키를 저장하는 레드-블랙 트리 기반의 맵입니다. <- 정렬된 맵 써야 된다면 얘로!!!
    2. Comparator를 제공하여 키의 정렬 순서를 사용자 정의할 수 있습니다.
    3. null 키는 기본적으로 허용되지 않습니다 (커스텀 Comparator에서 허용하도록 설정할 수 있습니다).
  3. LinkedHashMap
    1. HashMap과 유사하지만, 키-값 쌍을 추가한 순서대로 유지합니다. <- 정렬은 아니다! 그냥 순서 유지만 될 뿐!
    2. 캐시와 같이 순서가 중요한 상황에서 유용합니다.
    3. HashMap보다는 약간 느릴 수 있지만, 반복 작업에서는 더 빠른 성능을 보입니다.
  4. Hashtable
    1. HashMap과 비슷하지만, 모든 메서드가 스레드 안전합니다.
    2. 스레드 안전성을 위해 동기화(synchronization)가 사용되기 때문에, ConcurrentHashMap과 같은 최신 대안에 비해 성능이 떨어집니다.
    3. null 키나 null 값은 허용되지 않습니다.

코테 풀 때는 내가 형광펜 그어 놓은 저 부분만 생각하며 문제 풀어도 될 듯?


다음은 메서드를 알아야겠지?

주요 Map 인터페이스의 메서드

  • put(K key, V value): 지정된 키와 값을 맵에 저장합니다. 이미 키가 존재하면, 그 키의 값은 새 값으로 대체됩니다.
  • get(Object key): 지정된 키에 매핑된 값을 반환합니다. 키가 존재하지 않으면 null을 반환합니다.
  • remove(Object key): 지정된 키와 그 키에 매핑된 값을 제거합니다.
  • containsKey(Object key): 지정된 키가 맵에 존재하는지 확인합니다.
  • containsValue(Object value): 맵에서 특정 값을 가지는 항목이 있는지 확인합니다.
  • size(): 맵에 저장된 키-값 쌍의 수를 반환합니다.
  • isEmpty(): 맵이 비어 있는지 확인합니다.
  • keySet(): 맵의 모든 키를 포함하는 Set을 반환합니다.
  • values(): 맵의 모든 값을 컬렉션으로 반환합니다.
  • entrySet(): 맵의 모든 키-값 쌍을 포함하는 Set을 반환합니다.

그럼 HashMap, TreeMap, LinkedHashMap에서 추가된 메서드는 없을까?


HashMap

  1. clone(): HashMap의 얕은 복사본을 생성합니다. 이 메서드는 키와 값 객체 자체는 복사하지 않고, 키-값 매핑만 새로운 HashMap 객체로 복사합니다.
  2. loadFactor와 initialCapacity 설정:
    1. HashMap 생성자는 loadFactor와 initialCapacity 매개변수를 받을 수 있습니다. loadFactor는 HashMap이 얼마나 가득 차 있을 때 내부적으로 재해싱(크기 조절)이 일어나야 하는지를 결정하는 요소입니다. 기본값은 0.75이며, 이는 최적의 시간과 공간 비용의 균형을 제공합니다.
    2. initialCapacity는 HashMap 생성 시 초기 내부 배열의 크기를 설정합니다. 이 값을 적절히 설정하면 불필요한 재해싱을 줄일 수 있습니다.
  3. putIfAbsent(K key, V value): 지정된 키에 대해 현재 매핑된 값이 없을 경우에만 키-값 쌍을 HashMap에 추가합니다. 이는 Java 8에서 도입된 메서드로, 멀티 스레드 환경에서 유용하게 사용될 수 있습니다.
  4. computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction): 키가 아직 매핑되지 않은 경우, 제공된 함수를 사용하여 값을 계산하고 맵에 추가합니다. 이 메서드도 Java 8에서 추가되었습니다.
  5. computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction): 키가 이미 존재하는 경우, 제공된 함수를 사용하여 값을 재계산하고 맵에 저장합니다.
  6. forEach(BiConsumer<? super K, ? super V> action): 맵의 모든 키-값 쌍에 대해 주어진 작업을 수행합니다.

2번 빼고는 다 코테에서 요긴하게 쓰일 수 있는 것들 같다.


TreeMap

  1. 정렬된 키: TreeMap은 키를 자동으로 정렬하며, 이는 기본적으로 키의 자연 순서를 따르거나 생성자에 제공된 Comparator를 사용하여 정렬합니다.
  2. firstKey() / lastKey():
    1. firstKey(): 맵에서 가장 작은 키를 반환합니다.
    2. lastKey(): 맵에서 가장 큰 키를 반환합니다.
  3. higherKey(K key) / lowerKey(K key):
    1. higherKey(K key): 지정된 키보다 바로 다음으로 큰 키를 반환합니다.
    2. lowerKey(K key): 지정된 키보다 바로 이전에 있는 작은 키를 반환합니다.
  4. ceilingKey(K key) / floorKey(K key):
    1. ceilingKey(K key): 지정된 키와 같거나 그보다 큰 키 중에서 가장 작은 키를 반환합니다.
    2. floorKey(K key): 지정된 키와 같거나 그보다 작은 키 중에서 가장 큰 키를 반환합니다.
  5. subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive):
    1. 지정된 범위의 키들을 포함하는 새로운 맵 뷰를 반환합니다. fromInclusive와 toInclusive는 각각 시작 키와 종료 키의 포함 여부를 결정합니다.
  6. headMap(K toKey, boolean inclusive):
    1. 지정된 키 이하의 키들로 구성된 서브맵을 반환합니다. inclusive는 마지막 키의 포함 여부를 결정합니다.
  7. tailMap(K fromKey, boolean inclusive):
    1. 지정된 키 이상의 키들로 구성된 서브맵을 반환합니다. inclusive는 시작 키의 포함 여부를 결정합니다.

LinkedHashMap

 

LinkedHashMap은 HashMap을 확장하고, Map 인터페이스를 구현하면서, 항목들을 삽입 순서나 최근 접근 순서에 따라 유지하는 특별한 기능을 제공합니다. LinkedHashMap은 HashMap의 모든 메서드를 포함하며, 몇 가지 추가적인 특징을 제공합니다:

  1. 순서 유지: LinkedHashMap의 기본 생성자는 삽입된 순서대로 요소를 유지합니다. 이는 이터레이션 시 삽입된 순서대로 요소를 반환하게 합니다.
  2. 접근 순서 옵션: LinkedHashMap 생성자는 accessOrder 불린 매개변수를 받을 수 있습니다. 이 매개변수가 true로 설정되면, 맵은 최근에 접근(읽기 또는 쓰기)된 순서로 요소를 유지합니다. 이 기능은 캐싱 메커니즘에서 유용하게 사용될 수 있습니다, 가장 최근에 사용된 요소가 제일 나중에 제거되는 LRU (Least Recently Used) 캐시를 구현할 때 특히 그렇습니다.
  3. removeEldestEntry(Map.Entry<K,V> eldest): 이 메서드는 LinkedHashMap에 새 요소가 추가될 때마다 자동으로 호출됩니다. 이 메서드를 오버라이드하여, 맵이 특정 크기에 도달했을 때 가장 오래된 요소를 자동으로 제거할 수 있습니다. 기본적으로, 이 메서드는 항상 false를 반환하여 요소가 자동으로 제거되지 않도록 설정되어 있습니다. <- LinkedHashMap에 사이즈 제한이 있는 게 아니다. 사이즈 제한 설정이 가능하고 저 메서드를 오버라이드하면 사이즈를 넘어갈 때 가장 오래된 게 제거되게 할 수 있다는 말이다.
더보기

import java.util.LinkedHashMap;
import java.util.Map;

public class SampleLinkedHashMap {
    private static final int MAX_ENTRIES = 100;

    public static void main(String[] args) {
        LinkedHashMap<Integer, String> cache = new LinkedHashMap<Integer, String>(16, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<Integer, String> eldest) {
                return size() > MAX_ENTRIES;  // 맵의 크기가 최대 항목 수를 초과하면 가장 오래된 항목 제거
            }
        };

        // 캐시에 데이터 추가 예제
        for (int i = 0; i < 120; i++) {
            cache.put(i, "Value " + i);
        }

        // 출력해서 확인
        cache.forEach((key, value) -> System.out.println(key + ": " + value));
    }
}

이 외에도 LinkedHashMap은 HashMap에서 제공하는 모든 기능을 지원합니다 (put, get, remove, putAll, clear, containsKey, containsValue 등). LinkedHashMap의 주된 사용 이점은 순서에 의존적인 시나리오에서 유용하다는 것입니다, 예를 들어 순서가 중요한 데이터 목록을 유지하거나, 데이터 접근 패턴에 기반한 캐싱 솔루션을 구현할 때 효과적입니다.


자! 그러면 이제 코테 샘플과 각 클래스가 사용된 예를 볼 텐데

페이지가 너무 길어질 것 같으니 다음 글에서 만나요!