Pink Spider/Java에서 Hashmap과 LinkedHashMap의 차이

Created Sun, 20 Apr 2025 11:15:45 +0900 Modified Mon, 08 Dec 2025 08:41:47 +0900
1247 Words 6 min

HashMapLinkedHashMap은 둘 다 Java에서 제공하는 Map 인터페이스의 구현체이지만, 동작 방식과 특징에 몇 가지 중요한 차이점이 있습니다:


🔹 1. 저장 순서 유지 여부

특징 HashMap LinkedHashMap
순서 유지 ❌ 저장 순서 보장 안 됨 ✅ 입력 순서(또는 접근 순서) 유지
  • HashMap은 내부적으로 순서를 신경 쓰지 않기 때문에, 데이터를 삽입한 순서와 꺼낼 때 순서가 다를 수 있음.
  • LinkedHashMap은 이중 연결 리스트를 사용해 데이터를 저장한 순서를 기억함.

🔹 2. 성능

성능 HashMap LinkedHashMap
일반적으로 더 빠름 ❌ (조금 느림)
  • LinkedHashMap은 연결 리스트를 유지해야 하므로 약간의 오버헤드가 있음.

🔹 3. null 허용 여부

요소 HashMap LinkedHashMap
null 키 허용 1개 가능 1개 가능
null 값 허용 여러 개 가능 여러 개 가능

둘 다 null 키 하나와 null 값 여러 개 허용함.


🔹 4. 접근 순서 유지 모드 (LinkedHashMap만 해당)

  • LinkedHashMap은 생성자 옵션을 통해 “접근 순서"로 순서를 유지할 수도 있음:
LinkedHashMap<K, V> map = new LinkedHashMap<>(16, 0.75f, true);
  • 위처럼 accessOrder=true로 설정하면, get() 등으로 접근한 항목이 가장 뒤로 이동됨 → LRU 캐시 구현에 자주 사용.

🔹 5. 사용 예시

Map<String, String> hashMap = new HashMap<>();
hashMap.put("one", "1");
hashMap.put("two", "2");
hashMap.put("three", "3");
// 출력 순서: 랜덤

Map<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("one", "1");
linkedHashMap.put("two", "2");
linkedHashMap.put("three", "3");
// 출력 순서: one → two → three

✅ 정리 요약

항목 HashMap LinkedHashMap
순서 보장 ❌ 없음 ✅ 있음
성능 빠름 약간 느림
메모리 사용 적음 더 많음
사용 용도 일반적인 Map 순서가 중요한 경우, LRU 캐시 등

필요에 따라 선택하면 되는데, “순서가 중요하면 LinkedHashMap, **“성능과 메모리 절약이 중요하면 HashMap”**을 쓰면 됩니다.


✅ LRU 캐시 예제 (LinkedHashMap 사용)

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

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;

    // 생성자: accessOrder = true 설정하면 접근 순서로 정렬됨
    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    // 가장 오래된 항목을 자동으로 제거할지 결정하는 메서드 오버라이딩
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }

    // 테스트용 main 메서드
    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(3);

        cache.put(1, "One");
        cache.put(2, "Two");
        cache.put(3, "Three");
        System.out.println(cache); // {1=One, 2=Two, 3=Three}

        cache.get(1); // 1을 가장 최근으로 갱신
        cache.put(4, "Four"); // 2가 제거됨 (가장 오래 사용 안 됨)

        System.out.println(cache); // {3=Three, 1=One, 4=Four}
    }
}

💡 설명

  • super(capacity, 0.75f, true)
    trueaccess-order 모드, 즉 가장 최근에 접근한 순서를 유지.

  • removeEldestEntry()
    size()가 초과되면 자동으로 가장 오래된 항목 제거.


📌 출력 결과

{1=One, 2=Two, 3=Three}
{3=Three, 1=One, 4=Four}

2는 최근에 접근하지 않았기 때문에, 4가 추가되면서 제거됨. 딱 LRU 방식이죠.


이제 위에 만든 LRU 캐시를 스레드 세이프하게 만들고, 제너릭 타입을 고정한 버전도 보여드릴게요.


✅ 1. 스레드 세이프(Thread-safe) LRU 캐시

LinkedHashMap 자체는 스레드 안전하지 않으므로, synchronized 키워드 또는 Collections.synchronizedMap() 등을 활용해야 합니다.

🔸 방법 A: synchronized 키워드로 메서드 감싸기

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

public class ThreadSafeLRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;

    public ThreadSafeLRUCache(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    protected synchronized boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }

    @Override
    public synchronized V get(Object key) {
        return super.get(key);
    }

    @Override
    public synchronized V put(K key, V value) {
        return super.put(key, value);
    }

    @Override
    public synchronized String toString() {
        return super.toString();
    }
}

✅ 사용 예시

public static void main(String[] args) {
    ThreadSafeLRUCache<Integer, String> cache = new ThreadSafeLRUCache<>(2);
    cache.put(1, "A");
    cache.put(2, "B");
    cache.get(1); // A가 최근
    cache.put(3, "C"); // B 제거

    System.out.println(cache); // {1=A, 3=C}
}

✅ 2. 제너릭 타입 고정 버전 (예: Integer 키, String 값)

public class IntStringLRUCache extends ThreadSafeLRUCache<Integer, String> {
    public IntStringLRUCache(int capacity) {
        super(capacity);
    }

    public static void main(String[] args) {
        IntStringLRUCache cache = new IntStringLRUCache(3);
        cache.put(10, "Apple");
        cache.put(20, "Banana");
        cache.get(10);
        cache.put(30, "Cherry");
        cache.put(40, "Durian");

        System.out.println(cache); // {10=Apple, 30=Cherry, 40=Durian}
    }
}

🧠 정리

버전 특징
기본 버전 빠르고 간단하지만 싱글스레드 전용
ThreadSafeLRUCache 멀티스레드 환경에서 안전
IntStringLRUCache 제너릭 타입을 Integer, String으로 고정