๐ŸŽฏ1์ฃผ์ฐจ Unit 6.5 โ€” TreeMap, LinkedHashMap

Psjยท1์ผ ์ „

F-lab

๋ชฉ๋ก ๋ณด๊ธฐ
45/52

๐ŸŽฏ Unit 6.5 โ€” TreeMap, LinkedHashMap โ˜…โ˜…

F-lab Java 1์ฃผ์ฐจ / Phase 6 / Unit 6.5 ๋ณธ๊ฒฉ ํ•™์Šต ์ž๋ฃŒ โ€” Phase 6 ๋งˆ๋ฌด๋ฆฌ!
9-์„น์…˜ ๋งˆ์Šคํ„ฐ ํ”„๋กฌํ”„ํŠธ ํ˜•์‹์œผ๋กœ ๊นŠ์ด ํŒŒํ—ค์นœ๋‹ค.

์„ ์ˆ˜ ์ง€์‹: Phase 4-5, Unit 6.1-6.4 (ํŠนํžˆ HashMap, ArrayList/LinkedList)
๋‹ค์Œ ํ•™์Šต: Phase 7 โ€” I/O & NIO (์˜ˆ์ •)

์ด Unit์˜ ์˜๋ฏธ: ์ˆœ์„œ๊ฐ€ ์žˆ๋Š” Map ์˜ ๋‘ ๊ฐ€์ง€ ๋‹ต.
TreeMap = ์ •๋ ฌ ์ˆœ์„œ (๋ฒ”์œ„ ์ฟผ๋ฆฌ). LinkedHashMap = ์‚ฝ์ž…/์ ‘๊ทผ ์ˆœ์„œ (LRU ์บ์‹œ).
๋ฉด์ ‘ ("TreeMap ์–ธ์ œ ์“ฐ๋‚˜?") + ์‹ค๋ฌด (LRU ์บ์‹œ 6์ค„ ๊ตฌํ˜„) ๋‘ ๋งˆ๋ฆฌ.


๐ŸŒ 1. ์„ธ์ƒ ์† ๋น„์œ 

TreeMap = "์‚ฌ์ „" / LinkedHashMap = "๋ฐฉ๋ช…๋ก"

์„ธ ๊ฐ€์ง€ Map ์„ ์ผ์ƒ ๋น„์œ ๋กœ ํ’€์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

HashMap = ์šฐ์ฒด๊ตญ ์‚ฌ๋ฌผํ•จ (๋ณต์Šต)

  • ๋น ๋ฅธ ์ ‘๊ทผ (O(1))
  • ์ˆœ์„œ ์—†์Œ โ€” ์‚ฌ๋ฌผํ•จ ๋ฒˆํ˜ธ ์ˆœ์„œ๋Œ€๋กœ ๋“ค์–ด์žˆ์ง€ ์•Š์Œ
  • ์‚ฌ๋ฌผํ•จ ์œ„์น˜๋งŒ ์•Œ๋ฉด ๋จ

TreeMap = ์˜์–ด ์‚ฌ์ „

[A]
  Apple โ€” ์‚ฌ๊ณผ
  Apricot โ€” ์‚ด๊ตฌ
[B]
  Banana โ€” ๋ฐ”๋‚˜๋‚˜
  Berry โ€” ๋ฒ ๋ฆฌ
[C]
  Cat โ€” ๊ณ ์–‘์ด
  Cherry โ€” ์ฒด๋ฆฌ
...

ํŠน์ง•:

  • ๋‹จ์–ด๋“ค์ด ํ•ญ์ƒ ์•ŒํŒŒ๋ฒณ ์ˆœ
  • "C ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋‹จ์–ด" โ†’ ์ฑ… ์ค‘๊ฐ„์œผ๋กœ ์ง์ ‘ โ†’ ๋ฒ”์œ„ ์ฟผ๋ฆฌ
  • "Banana ๋‹ค์Œ ๋‹จ์–ด" โ†’ ์ฆ‰์‹œ โ†’ ์ˆœ์„œ ํ™œ์šฉ
  • ์ƒˆ ๋‹จ์–ด ์ถ”๊ฐ€ โ†’ ์ •๋ ฌ๋œ ์œ„์น˜์— ๋ผ์›Œ๋„ฃ๊ธฐ (๋А๋ฆผ)

ํ•ต์‹ฌ: ์ •๋ ฌ๋œ ์ƒํƒœ ์œ ์ง€ + ๋ฒ”์œ„ ๊ฒ€์ƒ‰.


LinkedHashMap = ๋ฐฉ๋ช…๋ก

2024-12-01 14:30  Alice
2024-12-02 09:15  Bob
2024-12-02 11:45  Charlie
2024-12-03 16:20  David

ํŠน์ง•:

  • ๋ฐฉ๋ฌธ ์ˆœ์„œ๋Œ€๋กœ ๊ธฐ๋ก
  • "์„ธ ๋ฒˆ์งธ๋กœ ๋ฐฉ๋ฌธํ•œ ์‚ฌ๋žŒ?" โ†’ ์ˆœ์„œ๋Œ€๋กœ ์„ธ์„œ ๋‹ต
  • ์ƒˆ ๋ฐฉ๋ฌธ์ž ์ถ”๊ฐ€ โ†’ ๋งˆ์ง€๋ง‰์— ํ•œ ์ค„ ์ถ”๊ฐ€
  • ๋น ๋ฅธ ๊ฒ€์ƒ‰ + ์ˆœ์„œ ์œ ์ง€

ํ•ต์‹ฌ: ์‚ฝ์ž… ์ˆœ์„œ ๋˜๋Š” ์ ‘๊ทผ ์ˆœ์„œ ์œ ์ง€.


ํ•ต์‹ฌ ํ•œ ๋ฌธ์žฅ

"HashMap ์€ ์ˆœ์„œ ์—†์Œ (๋น ๋ฆ„), TreeMap ์€ ์ •๋ ฌ ์ˆœ์„œ (๋ฒ”์œ„), LinkedHashMap ์€ ์‚ฝ์ž…/์ ‘๊ทผ ์ˆœ์„œ (LRU)."

๋น„์œ  ์ •๋ฆฌ:

๋น„์œ ์ž๋ฐ” ํด๋ž˜์Šค์ˆœ์„œํŠน์ง•
์‚ฌ๋ฌผํ•จHashMap์—†์Œ๊ฐ€์žฅ ๋น ๋ฆ„
์˜์–ด ์‚ฌ์ „TreeMap์ •๋ ฌ๋ฒ”์œ„ ์ฟผ๋ฆฌ
๋ฐฉ๋ช…๋กLinkedHashMap์‚ฝ์ž…/์ ‘๊ทผLRU ์บ์‹œ

๋˜ ๋‹ค๋ฅธ ๋น„์œ  โ€” "๋„์„œ๊ด€ ์ฑ… ์ •๋ฆฌ"

HashMap = ์ž„์˜ ๋ฐฐ์น˜ ์ฑ…๊ฝ‚์ด:

  • ISBN ์œผ๋กœ ์ฆ‰์‹œ ์ฐพ์„ ์ˆ˜ ์žˆ์Œ
  • ๊ทธ๋Ÿฌ๋‚˜ "5,000์› ์ดํ•˜ ์ฑ…" ๊ฐ™์€ ๋ฒ”์œ„ ๊ฒ€์ƒ‰ X

TreeMap = ๋ถ„๋ฅ˜ ์ •๋ฆฌ๋œ ์ฑ…๊ฝ‚์ด:

  • ๊ฐ€๊ฒฉ์ˆœ/์ œ๋ชฉ์ˆœ์œผ๋กœ ์ •๋ ฌ
  • "5,000์›~10,000์› ์ฑ…" ์ฆ‰์‹œ ์ฐพ๊ธฐ ๊ฐ€๋Šฅ
  • "๋งˆ์ง€๋ง‰์— ์‚ฐ ์ฑ…" ๋„ ์•Œ ์ˆ˜ ์žˆ์Œ

LinkedHashMap = ๋„์ฐฉ ์ˆœ์„œ ์ฑ…๊ฝ‚์ด:

  • ๊ตฌ๋งค ์ˆœ์„œ๋Œ€๋กœ ๋ฐฐ์น˜
  • "์ตœ๊ทผ ๊ตฌ๋งค" ์ถ”์  ๊ฐ€๋Šฅ
  • LRU (์ž์ฃผ ์•ˆ ๋ณธ ์ฑ… ์ฒ˜๋ถ„) ์ •์ฑ… ๊ตฌํ˜„

๐Ÿ”ฅ 2. ํƒ„์ƒ ๋ฐฐ๊ฒฝ

HashMap ๋งŒ์œผ๋กœ ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ๋“ค

HashMap ์€ ๋น ๋ฅด์ง€๋งŒ ์ˆœ์„œ ์ •๋ณด๊ฐ€ ์—†์Œ.

๋ฌธ์ œ 1 โ€” "๋ฒ”์œ„ ๊ฒ€์ƒ‰์ด ํ•„์š”ํ•ด"

Map<Integer, Customer> customers = new HashMap<>();
customers.put(20, alice);
customers.put(35, bob);
customers.put(40, charlie);
customers.put(55, david);

// 30~50๋Œ€ ๊ณ ๊ฐ๋งŒ ๊ฐ€์ ธ์˜ค๊ธฐ?
// HashMap ์œผ๋กœ๋Š” โ€” ์ „์ฒด ์ˆœํšŒ + ํ•„ํ„ฐ๋ง โ†’ O(n)

โ†’ ์ˆœ์„œ๊ฐ€ ์žˆ๋‹ค๋ฉด O(log n + k) ๋กœ ๊ฐ€๋Šฅ.


๋ฌธ์ œ 2 โ€” "์‚ฝ์ž… ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•ด"

// LRU ์บ์‹œ ์‹œ๋‚˜๋ฆฌ์˜ค
// ๊ฐ€์žฅ ์˜ค๋ž˜ ์•ˆ ์“ด ํ•ญ๋ชฉ ์ œ๊ฑฐ
Map<String, Cache> cache = new HashMap<>();
cache.put("A", a);  // 1๋ฒˆ์งธ
cache.put("B", b);  // 2๋ฒˆ์งธ
cache.put("C", c);  // 3๋ฒˆ์งธ

// "๊ฐ€์žฅ ์˜ค๋ž˜๋œ ํ•ญ๋ชฉ" โ†’ ์–ด๋–ป๊ฒŒ?
// HashMap ์€ ์ˆœ์„œ ์—†์Œ โ†’ ์ถ”์  ๋ถˆ๊ฐ€

โ†’ ์‚ฝ์ž… ์ˆœ์„œ๊ฐ€ ์œ ์ง€๋œ๋‹ค๋ฉด ์ฆ‰์‹œ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ.


๋ฌธ์ œ 3 โ€” "์ •๋ ฌ๋œ ์ถœ๋ ฅ์ด ํ•„์š”ํ•ด"

Map<String, Integer> wordCount = new HashMap<>();
wordCount.put("apple", 5);
wordCount.put("banana", 3);
wordCount.put("cherry", 8);

// ์•ŒํŒŒ๋ฒณ ์ˆœ์„œ๋กœ ์ถœ๋ ฅ?
// HashMap ์œผ๋กœ๋Š” โ€” keys โ†’ ์ •๋ ฌ โ†’ ๋งค๋ฒˆ O(n log n)

โ†’ ํ•ญ์ƒ ์ •๋ ฌ๋˜์–ด ์žˆ๋‹ค๋ฉด O(n) ์œผ๋กœ ๋.


์ž๋ฐ”์˜ ๋‘ ๊ฐ€์ง€ ๋‹ต

๋‹ต 1: TreeMap โ€” "์ •๋ ฌ ์ˆœ์„œ"

// Java 1.2 (1998)
public class TreeMap<K, V> implements NavigableMap<K, V> {
    private final Comparator<? super K> comparator;
    private transient Entry<K,V> root;  // Red-Black Tree
    private transient int size;
}

ํ•ต์‹ฌ:

  • Red-Black Tree ๊ธฐ๋ฐ˜
  • ํ•ญ์ƒ ์ •๋ ฌ๋œ ์ƒํƒœ ์œ ์ง€
  • ๋ฒ”์œ„ ์ฟผ๋ฆฌ ๋ฉ”์„œ๋“œ (subMap, headMap, tailMap, floorKey, ceilingKey)

๋‹ต 2: LinkedHashMap โ€” "์‚ฝ์ž…/์ ‘๊ทผ ์ˆœ์„œ"

// Java 1.4 (2002)
public class LinkedHashMap<K, V> extends HashMap<K, V> {
    transient LinkedHashMap.Entry<K,V> head;  // ์ฒซ ์š”์†Œ
    transient LinkedHashMap.Entry<K,V> tail;  // ๋งˆ์ง€๋ง‰ ์š”์†Œ
    final boolean accessOrder;  // false = ์‚ฝ์ž… ์ˆœ์„œ, true = ์ ‘๊ทผ ์ˆœ์„œ
    
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;  // ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ
    }
}

ํ•ต์‹ฌ:

  • HashMap ์ƒ์† + ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ
  • ๋‘ ๊ฐ€์ง€ ๋ชจ๋“œ:
    • accessOrder=false (๊ธฐ๋ณธ): ์‚ฝ์ž… ์ˆœ์„œ ์œ ์ง€
    • accessOrder=true: ์ ‘๊ทผ ์ˆœ์„œ ์œ ์ง€ (LRU ๊ตฌํ˜„ ๊ฐ€๋Šฅ)

์‹œ๊ฐ„ ๋ณต์žก๋„ ๋น„๊ต

์—ฐ์‚ฐHashMapTreeMapLinkedHashMap
getO(1) โญO(log n)O(1) โญ
putO(1) โญO(log n)O(1) โญ
removeO(1) โญO(log n)O(1) โญ
containsKeyO(1) โญO(log n)O(1) โญ
firstKey/lastKeyXO(log n) โญO(1) (์ฒซ/๋งˆ์ง€๋ง‰)
subMap (๋ฒ”์œ„)XO(log n + k) โญX
์ˆœํšŒ (์ˆœ์„œ)๋ณด์žฅ X์ •๋ ฌ ์ˆœ์„œ โญ์‚ฝ์ž…/์ ‘๊ทผ ์ˆœ์„œ โญ

โ†’ TreeMap ์€ ์ •๋ ฌ ๋ฉ”์„œ๋“œ, LinkedHashMap ์€ ์ˆœ์„œ + ๊ฐ™์€ ์„ฑ๋Šฅ.


๋ฉ”๋ชจ๋ฆฌ ๋น„๊ต

ํ•ญ๋ชฉHashMapTreeMapLinkedHashMap
Node ํฌ๊ธฐ32 ๋ฐ”์ดํŠธ40 ๋ฐ”์ดํŠธ (parent ์ถ”๊ฐ€)48 ๋ฐ”์ดํŠธ (before/after ์ถ”๊ฐ€)
๋ฉ”๋ชจ๋ฆฌ/์š”์†Œ1๋ฐฐ1.25๋ฐฐ1.5๋ฐฐ

โ†’ LinkedHashMap ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ๊ฐ€์žฅ ํผ, ๊ทธ๋Ÿฌ๋‚˜ ์ˆœ์„œ ์œ ์ง€์˜ ๊ฐ€์น˜ ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ.


ํ•ต์‹ฌ ํ†ต์ฐฐ

"๊ฐ™์€ Map ์ธํ„ฐํŽ˜์ด์Šค, ์„ธ ๊ฐ€์ง€ ๋‹ค๋ฅธ trade-off."

์†๋„๋งŒ ํ•„์š”ํ•˜๋ฉด HashMap, ์ •๋ ฌ๊ณผ ๋ฒ”์œ„๊ฐ€ ํ•„์š”ํ•˜๋ฉด TreeMap, ์‚ฝ์ž… ์ˆœ์„œ๋‚˜ LRU ๊ฐ€ ํ•„์š”ํ•˜๋ฉด LinkedHashMap. ์ƒํ™ฉ์— ๋งž๋Š” ์„ ํƒ ์ด ์‹œ๋‹ˆ์–ด ์ž๋ฐ” ๊ฐœ๋ฐœ์ž์˜ ์—ญ๋Ÿ‰.


๐Ÿ’ฃ 3. ์—†์œผ๋ฉด ์ƒ๊ธฐ๋Š” ๋ฌธ์ œ

์‹œ๋‚˜๋ฆฌ์˜ค 1: ILIC ์˜ ๊ฐ€๊ฒฉ๋Œ€๋ณ„ ํ™”๋ฌผ ๊ฒ€์ƒ‰

@Service
public class CargoSearchService {
    
    // โŒ HashMap ์œผ๋กœ ๊ฐ€๊ฒฉ๋Œ€ ๊ฒ€์ƒ‰
    private Map<Integer, List<Cargo>> cargosByFare = new HashMap<>();
    
    public List<Cargo> findByFareRange(int minFare, int maxFare) {
        List<Cargo> result = new ArrayList<>();
        for (Map.Entry<Integer, List<Cargo>> entry : cargosByFare.entrySet()) {
            // โš ๏ธ ๋ชจ๋“  ๊ฐ€๊ฒฉ๋Œ€ ์ˆœํšŒ โ†’ O(n)
            if (entry.getKey() >= minFare && entry.getKey() <= maxFare) {
                result.addAll(entry.getValue());
            }
        }
        return result;
    }
}

๋ฌธ์ œ:

  • 1๋งŒ ๊ฐœ ๊ฐ€๊ฒฉ๋Œ€ โ†’ 1๋งŒ ๋ฒˆ ๋น„๊ต
  • ๋งค๋ฒˆ O(n)

ํ•ด๊ฒฐ: TreeMap

private NavigableMap<Integer, List<Cargo>> cargosByFare = new TreeMap<>();

public List<Cargo> findByFareRange(int minFare, int maxFare) {
    // O(log n + k) โ€” ์ •๋ ฌ๋œ ํŠธ๋ฆฌ์—์„œ ๋ฒ”์œ„ ๊ฒ€์ƒ‰
    return cargosByFare.subMap(minFare, true, maxFare, true)
        .values().stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());
}

์‹œ๋‚˜๋ฆฌ์˜ค 2: LRU ์บ์‹œ โ€” ์ง์ ‘ ๊ตฌํ˜„์˜ ํ•จ์ •

// โŒ HashMap + List ๋กœ LRU ๊ตฌํ˜„ ์‹œ๋„
public class BadLRUCache<K, V> {
    private Map<K, V> map = new HashMap<>();
    private List<K> accessOrder = new ArrayList<>();
    private int capacity;
    
    public V get(K key) {
        V value = map.get(key);
        if (value != null) {
            accessOrder.remove(key);  // โš ๏ธ O(n)
            accessOrder.add(key);
        }
        return value;
    }
    
    public void put(K key, V value) {
        if (map.size() >= capacity && !map.containsKey(key)) {
            K oldest = accessOrder.remove(0);  // โš ๏ธ O(n)
            map.remove(oldest);
        }
        map.put(key, value);
        accessOrder.remove(key);  // O(n)
        accessOrder.add(key);
    }
}

๋ฌธ์ œ:

  • ๋งค get/put ๋งˆ๋‹ค O(n)
  • 1๋งŒ ๊ฐœ ์บ์‹œ โ†’ ๋งค ์ž‘์—… 1๋งŒ ๋ฒˆ ๋น„๊ต

ํ•ด๊ฒฐ: LinkedHashMap

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;
    
    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);  // accessOrder = true!
        this.capacity = capacity;
    }
    
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }
}
// 6 ์ค„๋กœ LRU ์บ์‹œ ์™„์„ฑ! ๋ชจ๋“  ์ž‘์—… O(1)

์‹œ๋‚˜๋ฆฌ์˜ค 3: ILIC ์˜ ์‹œ๊ฐ„์ˆœ ํ™œ๋™ ๋กœ๊ทธ

// โŒ HashMap โ€” ์ˆœ์„œ ๋ณด์กด X
Map<LocalDateTime, Activity> activities = new HashMap<>();
activities.put(time1, a1);
activities.put(time2, a2);
activities.put(time3, a3);

// ์‹œ๊ฐ„์ˆœ ์ถœ๋ ฅ?
for (Activity a : activities.values()) {
    System.out.println(a);  // ์ˆœ์„œ ๋ณด์žฅ X!
}

ํ•ด๊ฒฐ 1: TreeMap (์‹œ๊ฐ„์ˆœ ์ •๋ ฌ)

NavigableMap<LocalDateTime, Activity> activities = new TreeMap<>();
// ์ž๋™์œผ๋กœ ์‹œ๊ฐ„์ˆœ
for (Activity a : activities.values()) { ... }  // ์‹œ๊ฐ„์ˆœ ์ถœ๋ ฅ

ํ•ด๊ฒฐ 2: LinkedHashMap (์‚ฝ์ž… ์ˆœ์„œ)

Map<LocalDateTime, Activity> activities = new LinkedHashMap<>();
// ์‚ฝ์ž… ์ˆœ์„œ ์œ ์ง€ (์‹œ๊ฐ„์ˆœ ์‚ฝ์ž…ํ–ˆ๋‹ค๋ฉด ์‹œ๊ฐ„์ˆœ)

์‹œ๋‚˜๋ฆฌ์˜ค 4: ๋ฉด์ ‘ ๋‹จ๊ณจ ์งˆ๋ฌธ

Q: "TreeMap ์€ ์–ธ์ œ ์“ฐ๋‚˜์š”?"
A: "์Œ... HashMap ๋ณด๋‹ค ์ •๋ ฌ๋˜๋Š” ๊ฑฐ์—์š”"  โŒ ํ‘œ๋ฉด์ 

์‹œ๋‹ˆ์–ด ๋‹ต๋ณ€:
1. ๋ฒ”์œ„ ์ฟผ๋ฆฌ ๊ฐ€ ํ•„์š”ํ•  ๋•Œ (subMap, headMap, tailMap)
2. floorKey/ceilingKey ๊ฐ™์€ ๊ทผ์ ‘ ํ‚ค ๊ฒ€์ƒ‰
3. ์ •๋ ฌ๋œ ์ถœ๋ ฅ ์ด ์ž์ฃผ ํ•„์š”ํ•  ๋•Œ
4. NavigableMap ์˜ ๊ฐ•๋ ฅํ•œ ๋ฉ”์„œ๋“œ๋“ค
5. trade-off: O(log n) ๋น„์šฉ


์‹œ๋‚˜๋ฆฌ์˜ค 5: ILIC ์˜ ์‹œ๊ฐ„๋Œ€๋ณ„ ์นด์šดํŠธ

// โŒ ๋งค๋ฒˆ ์ •๋ ฌ
Map<Integer, Integer> hourlyCount = new HashMap<>();
// ... ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ ...

// ์‹œ๊ฐ„์ˆœ ์ถœ๋ ฅ ์‹œ
List<Integer> sortedHours = new ArrayList<>(hourlyCount.keySet());
Collections.sort(sortedHours);  // ๋งค๋ฒˆ O(n log n)
for (int hour : sortedHours) {
    System.out.println(hour + ": " + hourlyCount.get(hour));
}

ํ•ด๊ฒฐ: TreeMap

Map<Integer, Integer> hourlyCount = new TreeMap<>();
// ์ž๋™ ์ •๋ ฌ
for (Map.Entry<Integer, Integer> e : hourlyCount.entrySet()) {
    System.out.println(e.getKey() + ": " + e.getValue());
}

์‹œ๋‚˜๋ฆฌ์˜ค 6: API ์‘๋‹ต ์ˆœ์„œ ๋ณด์กด

// JSON ์‘๋‹ต์—์„œ ํ‚ค ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•œ ๊ฒฝ์šฐ
@RestController
public class ReportController {
    
    @GetMapping("/report")
    public Map<String, Object> getReport() {
        Map<String, Object> result = new HashMap<>();
        result.put("totalCount", 1000);
        result.put("avgFare", 50000);
        result.put("maxWeight", 5000);
        
        return result;
        // JSON ์ˆœ์„œ๊ฐ€ ๋ฌด์ž‘์œ„!
        // {"avgFare":50000, "maxWeight":5000, "totalCount":1000}
    }
    
    // โœ“ LinkedHashMap โ€” ์‚ฝ์ž… ์ˆœ์„œ ์œ ์ง€
    @GetMapping("/report-ordered")
    public Map<String, Object> getReportOrdered() {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("totalCount", 1000);
        result.put("avgFare", 50000);
        result.put("maxWeight", 5000);
        
        return result;
        // JSON: {"totalCount":1000, "avgFare":50000, "maxWeight":5000}
    }
}

์˜ํ–ฅ ์ •๋ฆฌ

์‹œ๋‚˜๋ฆฌ์˜ค์ž˜๋ชป๋œ ์„ ํƒ์˜ฌ๋ฐ”๋ฅธ ์„ ํƒ
๊ฐ€๊ฒฉ๋Œ€ ๋ฒ”์œ„ ๊ฒ€์ƒ‰HashMap O(n)TreeMap O(log n + k)
LRU ์บ์‹œ์ง์ ‘ ๊ตฌํ˜„ O(n)LinkedHashMap O(1)
์‹œ๊ฐ„์ˆœ ์ถœ๋ ฅHashMap + ์ •๋ ฌ ๋งค๋ฒˆTreeMap ๋˜๋Š” LinkedHashMap
๋ฉด์ ‘ ๋‹ต๋ณ€ํ‘œ๋ฉด์ ์‹œ๋‹ˆ์–ด
API ์‘๋‹ต ์ˆœ์„œHashMap (๋ฌด์ž‘์œ„)LinkedHashMap

โ†’ ์ ์ ˆํ•œ Map ์„ ํƒ์€ ์‹œ๋‹ˆ์–ด์˜ ํ•ต์‹ฌ ์—ญ๋Ÿ‰.


โœ… 4. ํ•ด๊ฒฐ์ฑ… โ€” ๋‘ Map ์˜ ์ •ํ™•ํ•œ ์ดํ•ด

TreeMap โ€” ์ •๋ ฌ๋œ Map

๊ธฐ๋ณธ ์‚ฌ์šฉ

TreeMap<String, Integer> map = new TreeMap<>();
map.put("Charlie", 3);
map.put("Alice", 1);
map.put("Bob", 2);

// ์ˆœํšŒ โ†’ ์ •๋ ฌ๋œ ์ˆœ์„œ!
for (Map.Entry<String, Integer> e : map.entrySet()) {
    System.out.println(e.getKey() + ": " + e.getValue());
}
// ์ถœ๋ ฅ:
// Alice: 1
// Bob: 2
// Charlie: 3

NavigableMap<Integer, String> map = new TreeMap<>();
map.put(10, "A");
map.put(20, "B");
map.put(30, "C");
map.put(40, "D");
map.put(50, "E");

// === ๋ฒ”์œ„ ๊ฒ€์ƒ‰ ===
map.subMap(20, 40);              // {20=B, 30=C} (40 ๋ฏธํฌํ•จ)
map.subMap(20, true, 40, true);  // {20=B, 30=C, 40=D}
map.headMap(30);                 // {10=A, 20=B}
map.tailMap(30);                 // {30=C, 40=D, 50=E}

// === ๊ทผ์ ‘ ํ‚ค ๊ฒ€์ƒ‰ ===
map.floorKey(25);    // 25 ์ดํ•˜ ์ค‘ ๊ฐ€์žฅ ํฐ ํ‚ค โ†’ 20
map.ceilingKey(25);  // 25 ์ด์ƒ ์ค‘ ๊ฐ€์žฅ ์ž‘์€ ํ‚ค โ†’ 30
map.lowerKey(20);    // 20 ๋ฏธ๋งŒ ์ค‘ ๊ฐ€์žฅ ํฐ ํ‚ค โ†’ 10
map.higherKey(20);   // 20 ์ดˆ๊ณผ ์ค‘ ๊ฐ€์žฅ ์ž‘์€ ํ‚ค โ†’ 30

// === ์ฒซ / ๋งˆ์ง€๋ง‰ ===
map.firstKey();      // 10
map.lastKey();       // 50
map.firstEntry();    // (10, A)
map.lastEntry();     // (50, E)
map.pollFirstEntry();  // (10, A) ๋ฐ˜ํ™˜ + ์ œ๊ฑฐ
map.pollLastEntry();   // (50, E) ๋ฐ˜ํ™˜ + ์ œ๊ฑฐ

// === ์—ญ์ˆœ ===
map.descendingMap();      // ์—ญ์ˆœ Map
map.descendingKeySet();   // ์—ญ์ˆœ ํ‚ค

์ •๋ ฌ ๊ธฐ์ค€ ์„ค์ • โ€” Comparator

// 1. ์ž์—ฐ ์ˆœ์„œ (Comparable)
TreeMap<String, Integer> ascMap = new TreeMap<>();  // A, B, C...

// 2. ์—ญ์ˆœ
TreeMap<String, Integer> descMap = new TreeMap<>(Comparator.reverseOrder());

// 3. ์ปค์Šคํ…€ Comparator
TreeMap<Customer, Order> byAge = new TreeMap<>(
    Comparator.comparingInt(Customer::getAge)
);

// 4. ๋‹ค์ค‘ ์ •๋ ฌ
TreeMap<Customer, Order> byGradeThenName = new TreeMap<>(
    Comparator.comparing(Customer::getGrade)
        .thenComparing(Customer::getName)
);

LinkedHashMap โ€” ์ˆœ์„œ ๋ณด์กด Map

๊ธฐ๋ณธ ์‚ฌ์šฉ โ€” ์‚ฝ์ž… ์ˆœ์„œ ๋ชจ๋“œ (๊ธฐ๋ณธ)

Map<String, Integer> map = new LinkedHashMap<>();
map.put("Charlie", 3);
map.put("Alice", 1);
map.put("Bob", 2);

// ์ˆœํšŒ โ†’ ์‚ฝ์ž… ์ˆœ์„œ!
for (Map.Entry<String, Integer> e : map.entrySet()) {
    System.out.println(e.getKey());
}
// ์ถœ๋ ฅ:
// Charlie  (๋จผ์ € ์‚ฝ์ž…)
// Alice
// Bob

์ ‘๊ทผ ์ˆœ์„œ ๋ชจ๋“œ โ€” LRU ์˜ ํ•ต์‹ฌ

// accessOrder = true
LinkedHashMap<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

// ํ˜„์žฌ ์ˆœ์„œ: A โ†’ B โ†’ C (์‚ฝ์ž… ์ˆœ์„œ)
map.get("A");  // A ์ ‘๊ทผ

// ์ˆœ์„œ ๋ณ€๊ฒฝ: B โ†’ C โ†’ A
// (A ๊ฐ€ ๊ฐ€์žฅ ์ตœ๊ทผ ์ ‘๊ทผ โ†’ ๋งจ ๋’ค๋กœ ์ด๋™)

ํ•ต์‹ฌ: get() ํ˜ธ์ถœ ์‹œ ๊ทธ ํ•ญ๋ชฉ์ด ๋งจ ๋’ค๋กœ ์ด๋™.


LRU ์บ์‹œ ๊ตฌํ˜„ โ€” 6 ์ค„ โญโญโญ

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;
    
    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);  // accessOrder = true
        this.capacity = capacity;
    }
    
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;  // capacity ์ดˆ๊ณผ ์‹œ ๊ฐ€์žฅ ์˜ค๋ž˜๋œ ์ œ๊ฑฐ
    }
}

// ์‚ฌ์šฉ
LRUCache<String, Customer> cache = new LRUCache<>(100);
cache.put("alice", aliceCustomer);
cache.get("alice");  // alice ๊ฐ€ ๊ฐ€์žฅ ์ตœ๊ทผ
// 100 ๊ฐœ ์ดˆ๊ณผ ์‹œ ์ž๋™์œผ๋กœ ๊ฐ€์žฅ ์˜ค๋ž˜ ์•ˆ ์“ด ํ•ญ๋ชฉ ์ œ๊ฑฐ

ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜:

  • accessOrder = true โ†’ get ์‹œ ๋งจ ๋’ค๋กœ ์ด๋™
  • removeEldestEntry ์˜ค๋ฒ„๋ผ์ด๋“œ โ†’ ์ž๋™ ์ œ๊ฑฐ ์ •์ฑ…
  • ๋ชจ๋“  ์ž‘์—… O(1)

๋น„๊ต ํ‘œ โญโญ (Map ์ข…ํ•ฉ)

ํ•ญ๋ชฉHashMapTreeMapLinkedHashMap
์ˆœ์„œ์—†์Œ์ •๋ ฌ์‚ฝ์ž…/์ ‘๊ทผ
์‹œ๊ฐ„ ๋ณต์žก๋„O(1)O(log n)O(1)
null key1๊ฐœโŒ1๊ฐœ
๋ฉ”๋ชจ๋ฆฌ/์š”์†Œ1๋ฐฐ1.25๋ฐฐ1.5๋ฐฐ
๋ฒ”์œ„ ์ฟผ๋ฆฌXโœ“ โญX
firstKey/lastKeyXO(log n)O(1)
LRU ๊ตฌํ˜„์–ด๋ ค์›€์–ด๋ ค์›€6์ค„ โญ
์‚ฌ์šฉ ์‹œ๊ธฐ๊ธฐ๋ณธ์ •๋ ฌ/๋ฒ”์œ„์ˆœ์„œ/LRU

์„ ํƒ ๊ฐ€์ด๋“œ โญ

Map ์ด ํ•„์š”ํ•˜๋‹ค
        โ†“
์ˆœ์„œ๊ฐ€ ํ•„์š”ํ•œ๊ฐ€?
โ”œโ”€โ”€ ๋ฌด๊ด€ โ†’ HashMap (๋˜๋Š” ConcurrentHashMap)
โ”œโ”€โ”€ ์ •๋ ฌ ์ˆœ์„œ / ๋ฒ”์œ„ ์ฟผ๋ฆฌ โ†’ TreeMap โญ
โ””โ”€โ”€ ์‚ฝ์ž… / ์ ‘๊ทผ ์ˆœ์„œ
    โ”œโ”€โ”€ ์‚ฝ์ž… ์ˆœ์„œ๋งŒ โ†’ LinkedHashMap (๊ธฐ๋ณธ)
    โ””โ”€โ”€ LRU ์บ์‹œ โ†’ LinkedHashMap(accessOrder=true) โญ

ํ”ํ•œ ํŒจํ„ด

ํŒจํ„ด 1: ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์ •๋ ฌ๋œ ๊ฒฐ๊ณผ

// ๋‹จ์–ด ๋นˆ๋„ โ†’ ์•ŒํŒŒ๋ฒณ์ˆœ ์ถœ๋ ฅ
TreeMap<String, Integer> wordCount = new TreeMap<>();
words.forEach(w -> wordCount.merge(w, 1, Integer::sum));

wordCount.forEach((word, count) -> 
    System.out.println(word + ": " + count)
);
// ์ž๋™์œผ๋กœ ์•ŒํŒŒ๋ฒณ์ˆœ!

ํŒจํ„ด 2: ๊ฐ€๊ฒฉ๋Œ€๋ณ„ ๊ฒ€์ƒ‰

// ๊ฐ€๊ฒฉ๋Œ€๋ณ„ ํ™”๋ฌผ
NavigableMap<Integer, List<Cargo>> byFare = new TreeMap<>();

// 5,000์›~10,000์› ๋ฒ”์œ„
Map<Integer, List<Cargo>> mid = byFare.subMap(5000, 10001);

ํŒจํ„ด 3: LRU ์บ์‹œ

Map<String, Data> cache = new LinkedHashMap<>(100, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry<String, Data> eldest) {
        return size() > 100;
    }
};

ํŒจํ„ด 4: API ์‘๋‹ต ์ˆœ์„œ ๋ณด์กด

Map<String, Object> response = new LinkedHashMap<>();
response.put("status", "success");
response.put("data", ...);
response.put("timestamp", ...);
// JSON ์ถœ๋ ฅ ์ˆœ์„œ ๋ณด์žฅ

๐Ÿ—๏ธ 5. ๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ

TreeMap โ€” Red-Black Tree ๊ธฐ๋ฐ˜

๊ตฌ์กฐ

public class TreeMap<K, V> implements NavigableMap<K, V> {
    private final Comparator<? super K> comparator;
    private transient Entry<K,V> root;
    private transient int size;
    
    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;     // ์™ผ์ชฝ ์ž์‹
        Entry<K,V> right;    // ์˜ค๋ฅธ์ชฝ ์ž์‹
        Entry<K,V> parent;   // ๋ถ€๋ชจ
        boolean color = BLACK;  // Red ๋˜๋Š” Black
    }
}

Red-Black Tree ๊ทœ์น™:
1. ๋…ธ๋“œ๋Š” Red ๋˜๋Š” Black
2. Root ๋Š” Black
3. Red ๋…ธ๋“œ์˜ ์ž์‹์€ Black
4. ๋ชจ๋“  leaf ๊นŒ์ง€์˜ Black ๋…ธ๋“œ ์ˆ˜๋Š” ๊ฐ™์Œ
5. โ†’ ๊ท ํ˜• ๋ณด์žฅ: ์ตœ์•… ๊นŠ์ด โ‰ค 2 ร— log(n+1)


put() ์˜ ๋™์ž‘

public V put(K key, V value) {
    Entry<K,V> t = root;
    
    // 1. ๋ฃจํŠธ๊ฐ€ ์—†์œผ๋ฉด root ๋กœ
    if (t == null) {
        compare(key, key);  // null/type check
        root = new Entry<>(key, value, null);
        size = 1;
        return null;
    }
    
    // 2. ๋น„๊ตํ•˜๋ฉฐ ์œ„์น˜ ์ฐพ๊ธฐ
    Comparator<? super K> cpr = comparator;
    int cmp;
    Entry<K,V> parent;
    
    do {
        parent = t;
        cmp = cpr.compare(key, t.key);
        if (cmp < 0) t = t.left;
        else if (cmp > 0) t = t.right;
        else return t.setValue(value);  // ๊ฐ™์€ ํ‚ค โ†’ ๊ฐ’ ๊ต์ฒด
    } while (t != null);
    
    // 3. ์ƒˆ ๋…ธ๋“œ ์ถ”๊ฐ€
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0) parent.left = e;
    else parent.right = e;
    
    // 4. Red-Black ๊ท ํ˜• ์กฐ์ •
    fixAfterInsertion(e);
    size++;
    return null;
}

O(log n): ํŠธ๋ฆฌ ๊นŠ์ด๋งŒํผ ํƒ์ƒ‰.


subMap() ์˜ ๋™์ž‘

// map.subMap(20, 40)
// โ†’ 20 โ‰ค key < 40 ์ธ ๋ชจ๋“  entry

์•Œ๊ณ ๋ฆฌ์ฆ˜:
1. ์‹œ์ž‘ ํ‚ค (20) ์ฐพ๊ธฐ: O(log n)
2. ๋ ํ‚ค (40) ์ฐพ๊ธฐ: O(log n)
3. ์‚ฌ์ด์˜ ๋ชจ๋“  entry ์ˆœํšŒ: O(k)
4. ์ด: O(log n + k)

์ค‘์š”: subMap ์€ view โ€” ์›๋ณธ ๋ณ€๊ฒฝ ์‹œ ๋ฐ˜์˜๋จ.


Red-Black Tree ์˜ ๊ฐ•์ 

์ž‘์—…์‹œ๊ฐ„
์‚ฝ์ž…O(log n)
๊ฒ€์ƒ‰O(log n)
์‚ญ์ œO(log n)
์ตœ์†Œ/์ตœ๋Œ€O(log n)
๋ฒ”์œ„ ๊ฒ€์ƒ‰O(log n + k)

์ž๋ฐ” 8+ HashMap ์˜ Treeification ๋„ ๊ฐ™์€ Red-Black Tree.


LinkedHashMap โ€” HashMap + ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ

๊ตฌ์กฐ

public class LinkedHashMap<K,V> extends HashMap<K,V> {
    transient Entry<K,V> head;  // ์ฒซ ๋ฒˆ์งธ (๊ฐ€์žฅ ์˜ค๋ž˜๋œ)
    transient Entry<K,V> tail;  // ๋งˆ์ง€๋ง‰ (๊ฐ€์žฅ ์ตœ๊ทผ)
    final boolean accessOrder;  // false=์‚ฝ์ž…์ˆœ์„œ, true=์ ‘๊ทผ์ˆœ์„œ
    
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;  // ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ
    }
}

ํ•ต์‹ฌ:

  • HashMap ์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ ์œ ์ง€ (O(1) get/put)
  • ์ถ”๊ฐ€๋กœ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๋กœ ์ˆœ์„œ ์ถ”์ 

๋ฉ”๋ชจ๋ฆฌ ๊ทธ๋ฆผ

HashMap ๋ถ€๋ถ„ (table ๋ฐฐ์—ด):
table[0]: ...
table[3]: Node(B) โ†’ Node(D)
table[5]: Node(A)
table[7]: Node(C)
...

LinkedHashMap ์˜ ์–‘๋ฐฉํ–ฅ ๋ฆฌ์ŠคํŠธ:
head โ†’ A โ†” B โ†” C โ†” D โ† tail
       โ†‘               โ†‘
       ๊ฐ€์žฅ ์˜ค๋ž˜๋œ     ๊ฐ€์žฅ ์ตœ๊ทผ

๊ฐ Node ๋Š” next (HashMap) ์™€ before/after (LinkedHashMap) ๋ชจ๋‘ ๋ณด์œ 

put() ์˜ ๋™์ž‘ (์‚ฝ์ž… ์ˆœ์„œ)

// LinkedHashMap.newNode()
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<>(hash, key, value, e);
    linkNodeLast(p);  // โ˜… ์–‘๋ฐฉํ–ฅ ๋ฆฌ์ŠคํŠธ์˜ ๋์— ์ถ”๊ฐ€
    return p;
}

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}

ํšจ๊ณผ:

  • HashMap ์˜ put ๋™์ž‘ ๊ทธ๋Œ€๋กœ
    • ์–‘๋ฐฉํ–ฅ ๋ฆฌ์ŠคํŠธ ๋์— ์ถ”๊ฐ€
  • ์ˆœํšŒ ์‹œ head โ†’ after ๋”ฐ๋ผ๊ฐ€๋ฉด ์‚ฝ์ž… ์ˆœ์„œ

get() ์˜ ๋™์ž‘ (์ ‘๊ทผ ์ˆœ์„œ ๋ชจ๋“œ)

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);  // โ˜… ๋งจ ๋’ค๋กœ ์ด๋™
    return e.value;
}

void afterNodeAccess(Node<K,V> e) {
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        // e ๋ฅผ ํ˜„์žฌ ์œ„์น˜์—์„œ ๋นผ๊ณ  tail ๋กœ ์ด๋™
        LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e;
        LinkedHashMap.Entry<K,V> b = p.before, a = p.after;
        p.after = null;
        if (b == null) head = a;
        else b.after = a;
        if (a != null) a.before = b;
        else last = b;
        if (last == null) head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
    }
}

ํ•ต์‹ฌ:

  • ์ ‘๊ทผ๋œ ํ•ญ๋ชฉ์„ ์–‘๋ฐฉํ–ฅ ๋ฆฌ์ŠคํŠธ์˜ ๋ ์œผ๋กœ ์ด๋™
  • O(1) โ€” ์–‘๋ฐฉํ–ฅ ๋ฆฌ์ŠคํŠธ์˜ ์žฅ์ 
  • head ๋Š” ํ•ญ์ƒ ๊ฐ€์žฅ ์˜ค๋ž˜ ์•ˆ ์“ด ํ•ญ๋ชฉ

removeEldestEntry โ€” LRU ์˜ ๋งˆ๋ฒ•

// LinkedHashMap ์˜ ๋ฉ”์„œ๋“œ
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;  // ๊ธฐ๋ณธ: ์ œ๊ฑฐ ์•ˆ ํ•จ
}

// HashMap.afterNodeInsertion ์—์„œ ํ˜ธ์ถœ
void afterNodeInsertion(boolean evict) {
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

LRU ์บ์‹œ์˜ ๋™์ž‘:
1. put ํ˜ธ์ถœ โ†’ ์ƒˆ entry ์ถ”๊ฐ€
2. afterNodeInsertion ํ˜ธ์ถœ
3. removeEldestEntry ์˜ค๋ฒ„๋ผ์ด๋“œ ๊ฐ€ true ๋ฐ˜ํ™˜ ์‹œ
4. head (๊ฐ€์žฅ ์˜ค๋ž˜๋œ ํ•ญ๋ชฉ) ์ œ๊ฑฐ


accessOrder ์˜ ํšจ๊ณผ

๋ชจ๋“œ ๋น„๊ต:

// 1. ์‚ฝ์ž… ์ˆœ์„œ (accessOrder = false, ๊ธฐ๋ณธ)
Map<String, Integer> insertion = new LinkedHashMap<>();
insertion.put("A", 1);
insertion.put("B", 2);
insertion.put("C", 3);
insertion.get("A");  // ์ˆœ์„œ ๋ณ€๊ฒฝ X

// ์ˆœํšŒ: A, B, C (์‚ฝ์ž… ์ˆœ์„œ)

// 2. ์ ‘๊ทผ ์ˆœ์„œ (accessOrder = true)
Map<String, Integer> access = new LinkedHashMap<>(16, 0.75f, true);
access.put("A", 1);
access.put("B", 2);
access.put("C", 3);
access.get("A");  // A ๊ฐ€ ๋งจ ๋’ค๋กœ!

// ์ˆœํšŒ: B, C, A (์ ‘๊ทผ ์ˆœ์„œ)

LinkedHashMap vs LinkedHashSet

// LinkedHashSet ๋„ ๊ฐ™์€ ์›๋ฆฌ
Set<String> set = new LinkedHashSet<>();
set.add("C");
set.add("A");
set.add("B");

for (String s : set) {
    System.out.println(s);  // C, A, B (์‚ฝ์ž… ์ˆœ์„œ)
}

โ†’ HashSet + ์–‘๋ฐฉํ–ฅ ๋ฆฌ์ŠคํŠธ.


๐Ÿ’ป 6. ์‹ค์ „ ์ฝ”๋“œ ์˜ˆ์‹œ

์˜ˆ์‹œ 1: ILIC ์˜ ๊ฐ€๊ฒฉ๋Œ€๋ณ„ ํ™”๋ฌผ ๊ฒ€์ƒ‰ (TreeMap)

@Service
public class CargoFareService {
    
    private final NavigableMap<Integer, List<Cargo>> cargosByFare = new TreeMap<>();
    
    public void registerCargo(Cargo cargo) {
        cargosByFare
            .computeIfAbsent(cargo.getFare(), k -> new ArrayList<>())
            .add(cargo);
    }
    
    public List<Cargo> findByFareRange(int minFare, int maxFare) {
        // O(log n + k) โ€” ์ •๋ ฌ ํŠธ๋ฆฌ์—์„œ ๋ฒ”์œ„ ๊ฒ€์ƒ‰
        return cargosByFare.subMap(minFare, true, maxFare, true)
            .values().stream()
            .flatMap(List::stream)
            .collect(Collectors.toList());
    }
    
    public Cargo findCheapestAbove(int minFare) {
        // ๊ฐ€์žฅ ์ €๋ ดํ•œ ํ™”๋ฌผ ์ค‘ minFare ์ด์ƒ
        Map.Entry<Integer, List<Cargo>> entry = cargosByFare.ceilingEntry(minFare);
        return entry == null ? null : entry.getValue().get(0);
    }
    
    public Cargo findMostExpensive() {
        Map.Entry<Integer, List<Cargo>> entry = cargosByFare.lastEntry();
        return entry == null ? null : entry.getValue().get(0);
    }
}

์˜ˆ์‹œ 2: LRU ์บ์‹œ โ€” LinkedHashMap

@Service
public class FareCalculationCache {
    
    private final LRUCache<String, BigDecimal> cache = new LRUCache<>(1000);
    
    public BigDecimal getFare(String routeKey) {
        BigDecimal cached = cache.get(routeKey);
        if (cached != null) return cached;
        
        // ๋น„์‹ผ ๊ณ„์‚ฐ
        BigDecimal computed = fareCalculator.calculate(routeKey);
        cache.put(routeKey, computed);
        return computed;
    }
}

// LRU ์บ์‹œ โ€” 6 ์ค„!
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;
    
    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }
    
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }
}

ํšจ๊ณผ:

  • ๋ชจ๋“  get/put O(1)
  • 1000 ๊ฐœ ์ดˆ๊ณผ ์‹œ ์ž๋™ ์ œ๊ฑฐ (LRU)
  • ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ์ผ์ •

์˜ˆ์‹œ 3: ILIC ์˜ ์ผ์ž๋ณ„ ๋งค์ถœ (TreeMap)

@Service
public class DailySalesService {
    
    // ์ผ์ž -> ๋งค์ถœ
    private final NavigableMap<LocalDate, Long> dailySales = new TreeMap<>();
    
    public void recordSale(LocalDate date, long amount) {
        dailySales.merge(date, amount, Long::sum);
    }
    
    public Map<LocalDate, Long> getRecentDays(int days) {
        LocalDate today = LocalDate.now();
        LocalDate from = today.minusDays(days);
        // O(log n + k)
        return dailySales.subMap(from, true, today, true);
    }
    
    public Long getMaxSale() {
        return dailySales.values().stream()
            .max(Long::compareTo)
            .orElse(0L);
    }
    
    public LocalDate getMaxSaleDate() {
        return dailySales.entrySet().stream()
            .max(Map.Entry.comparingByValue())
            .map(Map.Entry::getKey)
            .orElse(null);
    }
    
    public void printChronological() {
        // ์ž๋™์œผ๋กœ ๋‚ ์งœ์ˆœ!
        dailySales.forEach((date, amount) ->
            System.out.println(date + ": " + amount + "์›"));
    }
}

์˜ˆ์‹œ 4: ์ƒ์œ„ N ๊ฐœ ์กฐํšŒ (TreeMap.descendingMap)

public class TopNAnalysis {
    
    public List<Map.Entry<String, Integer>> getTopCustomers(
            Map<String, Integer> ordersByCustomer, int n) {
        
        // ์ฃผ๋ฌธ ์ˆ˜ โ†’ ๊ณ ๊ฐ (์ •๋ ฌ ํŠธ๋ฆฌ)
        NavigableMap<Integer, List<String>> byCount = new TreeMap<>();
        
        ordersByCustomer.forEach((customer, count) ->
            byCount.computeIfAbsent(count, k -> new ArrayList<>()).add(customer));
        
        // ์ƒ์œ„ N ๊ฐœ
        List<Map.Entry<String, Integer>> top = new ArrayList<>();
        for (Map.Entry<Integer, List<String>> e : byCount.descendingMap().entrySet()) {
            for (String customer : e.getValue()) {
                if (top.size() >= n) return top;
                top.add(Map.entry(customer, e.getKey()));
            }
        }
        return top;
    }
}

์˜ˆ์‹œ 5: API ์‘๋‹ต ์ˆœ์„œ ๋ณด์กด (LinkedHashMap)

@RestController
public class CargoController {
    
    @GetMapping("/api/cargo/{id}")
    public Map<String, Object> getCargoDetails(@PathVariable Long id) {
        Cargo cargo = cargoService.findById(id);
        
        // ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•œ ์‘๋‹ต
        Map<String, Object> response = new LinkedHashMap<>();
        response.put("id", cargo.getId());
        response.put("name", cargo.getName());
        response.put("weight", cargo.getWeight());
        response.put("fare", cargo.getFare());
        response.put("originPort", cargo.getOriginPort());
        response.put("destinationPort", cargo.getDestinationPort());
        response.put("createdAt", cargo.getCreatedAt());
        
        return response;
        // JSON ์ถœ๋ ฅ ์ˆœ์„œ ๋ณด์žฅ!
    }
}

์˜ˆ์‹œ 6: ๋‹ค์ค‘ ์ •๋ ฌ ๊ธฐ์ค€ (Comparator)

public class CustomerSortExample {
    
    public NavigableMap<Customer, Order> sortByGradeAndName(
            Map<Customer, Order> orders) {
        
        // ๋“ฑ๊ธ‰ (๋†’์€ ๋“ฑ๊ธ‰ ์šฐ์„ ) โ†’ ์ด๋ฆ„ (๊ฐ€๋‚˜๋‹ค์ˆœ)
        Comparator<Customer> sorter = Comparator
            .comparing(Customer::getGrade, Comparator.reverseOrder())
            .thenComparing(Customer::getName);
        
        TreeMap<Customer, Order> sorted = new TreeMap<>(sorter);
        sorted.putAll(orders);
        return sorted;
    }
    
    public NavigableMap<Customer, Order> sortByFare(
            Map<Customer, Order> orders) {
        
        // ์šด์ž„ (๋†’์€ ์ˆœ)
        Comparator<Customer> byFare = (a, b) -> {
            long fareA = orders.get(a).getFare();
            long fareB = orders.get(b).getFare();
            return Long.compare(fareB, fareA);  // ๋‚ด๋ฆผ์ฐจ์ˆœ
        };
        
        TreeMap<Customer, Order> sorted = new TreeMap<>(byFare);
        sorted.putAll(orders);
        return sorted;
    }
}

์˜ˆ์‹œ 7: LinkedHashMap ์œผ๋กœ ์บ์‹œ + ํ†ต๊ณ„

public class CacheWithStats<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;
    private long hits = 0;
    private long misses = 0;
    
    public CacheWithStats(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }
    
    @Override
    public V get(Object key) {
        V value = super.get(key);
        if (value != null) hits++;
        else misses++;
        return value;
    }
    
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }
    
    public double getHitRate() {
        long total = hits + misses;
        return total == 0 ? 0 : (double) hits / total;
    }
    
    public void printStats() {
        System.out.printf("Hits: %d, Misses: %d, HitRate: %.2f%%%n",
            hits, misses, getHitRate() * 100);
    }
}

์˜ˆ์‹œ 8: TreeMap ์œผ๋กœ ๊ฐ€๊ฒฉ ํ˜ธ๊ฐ€์ฐฝ

public class OrderBook {
    
    // ๋งค์ˆ˜ ์ฃผ๋ฌธ (๊ฐ€๊ฒฉ ๋†’์€ ์ˆœ์œผ๋กœ ์ •๋ ฌ)
    private final NavigableMap<BigDecimal, Long> bids = new TreeMap<>(Comparator.reverseOrder());
    
    // ๋งค๋„ ์ฃผ๋ฌธ (๊ฐ€๊ฒฉ ๋‚ฎ์€ ์ˆœ์œผ๋กœ ์ •๋ ฌ)
    private final NavigableMap<BigDecimal, Long> asks = new TreeMap<>();
    
    public void addBid(BigDecimal price, long quantity) {
        bids.merge(price, quantity, Long::sum);
    }
    
    public void addAsk(BigDecimal price, long quantity) {
        asks.merge(price, quantity, Long::sum);
    }
    
    public BigDecimal getBestBid() {
        return bids.isEmpty() ? null : bids.firstKey();  // ๊ฐ€์žฅ ๋†’์€ ๋งค์ˆ˜ ๊ฐ€๊ฒฉ
    }
    
    public BigDecimal getBestAsk() {
        return asks.isEmpty() ? null : asks.firstKey();  // ๊ฐ€์žฅ ๋‚ฎ์€ ๋งค๋„ ๊ฐ€๊ฒฉ
    }
    
    public BigDecimal getSpread() {
        BigDecimal bestBid = getBestBid();
        BigDecimal bestAsk = getBestAsk();
        if (bestBid == null || bestAsk == null) return null;
        return bestAsk.subtract(bestBid);
    }
    
    public Map<BigDecimal, Long> getTop5Bids() {
        return bids.entrySet().stream()
            .limit(5)
            .collect(Collectors.toMap(
                Map.Entry::getKey, Map.Entry::getValue,
                (a, b) -> a, LinkedHashMap::new));
        // LinkedHashMap โ†’ ์ˆœ์„œ ๋ณด์กด
    }
}

โš ๏ธ 7. ์ฃผ์˜์‚ฌํ•ญ & ํ”ํ•œ ์‹ค์ˆ˜

์‹ค์ˆ˜ 1: TreeMap ์— null ํ‚ค

TreeMap<String, Integer> map = new TreeMap<>();
map.put(null, 1);  // โŒ NullPointerException

์ด์œ : ์ •๋ ฌ์„ ์œ„ํ•ด ํ‚ค ๋น„๊ต โ†’ null ๋น„๊ต ๋ถˆ๊ฐ€.

ํ•ด๊ฒฐ: HashMap ๋˜๋Š” LinkedHashMap ์‚ฌ์šฉ (null ํ‚ค 1๊ฐœ ํ—ˆ์šฉ).


์‹ค์ˆ˜ 2: Comparable ๋ฏธ๊ตฌํ˜„ ํ‚ค

class Customer {
    String customerId;
}

TreeMap<Customer, String> map = new TreeMap<>();
map.put(new Customer(), "value");  // โŒ ClassCastException

์ด์œ : Customer ๊ฐ€ Comparable ๋ฏธ๊ตฌํ˜„ โ†’ ์ •๋ ฌ ๋ถˆ๊ฐ€.

ํ•ด๊ฒฐ 1: Comparable ๊ตฌํ˜„

class Customer implements Comparable<Customer> {
    @Override
    public int compareTo(Customer other) {
        return this.customerId.compareTo(other.customerId);
    }
}

ํ•ด๊ฒฐ 2: Comparator ์ „๋‹ฌ

TreeMap<Customer, String> map = new TreeMap<>(
    Comparator.comparing(Customer::getCustomerId)
);

์‹ค์ˆ˜ 3: subMap ์˜ view ํ•จ์ •

NavigableMap<Integer, String> map = new TreeMap<>();
map.put(10, "A"); map.put(20, "B"); map.put(30, "C");

Map<Integer, String> sub = map.subMap(15, 25);  // {20=B}

map.put(25, "X");  // ์›๋ณธ ์ˆ˜์ •
System.out.println(sub);  // {20=B, 25=X} โ€” view ๊ฐ€ ๊ฐฑ์‹ ๋จ!

// ์ˆ˜์ • ์‹œ ์›๋ณธ๋„ ๋ณ€๊ฒฝ
sub.put(22, "Y");
System.out.println(map);  // {10=A, 20=B, 22=Y, 25=X, 30=C}

์›์น™: subMap ์€ view. ๋…๋ฆฝ ์‚ฌ๋ณธ ํ•„์š” ์‹œ ๋ณต์‚ฌ.

Map<Integer, String> copy = new TreeMap<>(map.subMap(15, 25));

์‹ค์ˆ˜ 4: LinkedHashMap ์˜ accessOrder ๋ง๊ฐ

// LRU ์˜๋„
Map<String, Integer> cache = new LinkedHashMap<>(100);  // โŒ ๊ธฐ๋ณธ = ์‚ฝ์ž… ์ˆœ์„œ
cache.put("A", 1);
cache.get("A");  // ์ˆœ์„œ ๋ณ€๊ฒฝ ์•ˆ ๋จ!

ํ•ด๊ฒฐ: ์ƒ์„ฑ์ž ๋ช…์‹œ

new LinkedHashMap<>(100, 0.75f, true);  // accessOrder = true

์‹ค์ˆ˜ 5: removeEldestEntry ๋ฏธ์˜ค๋ฒ„๋ผ์ด๋“œ

// LRU ์บ์‹œ ์‹œ๋„
Map<String, Data> cache = new LinkedHashMap<>(100, 0.75f, true);
// โŒ ์ž๋™ ์ œ๊ฑฐ ์•ˆ ๋จ!
// removeEldestEntry ๊ฐ€ ํ•ญ์ƒ false ๋ฐ˜ํ™˜ (๊ธฐ๋ณธ๊ฐ’)

ํ•ด๊ฒฐ: ์ต๋ช… ํด๋ž˜์Šค ๋˜๋Š” ์ƒ์†

Map<String, Data> cache = new LinkedHashMap<>(100, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry<String, Data> eldest) {
        return size() > 100;
    }
};

์‹ค์ˆ˜ 6: TreeMap ์˜ O(log n) ๋น„์šฉ ๋ฌด์‹œ

// โŒ ๋น ๋ฅธ lookup ์ด ํ•„์š”ํ•œ๋ฐ TreeMap ์‚ฌ์šฉ
Map<String, Customer> lookup = new TreeMap<>();
for (int i = 0; i < 1_000_000; i++) {
    lookup.get(keys[i]);  // O(log n) ร— 100๋งŒ โ‰ˆ 2์ฒœ๋งŒ
}

ํ•ด๊ฒฐ: ์ •๋ ฌ/๋ฒ”์œ„ ํ•„์š” ์—†์œผ๋ฉด HashMap.

Map<String, Customer> lookup = new HashMap<>();  // O(1) ร— 100๋งŒ = 100๋งŒ

์‹ค์ˆ˜ 7: LinkedHashMap ์˜ ๋ฉ”๋ชจ๋ฆฌ ๋ง๊ฐ

// 100๋งŒ ๋ฐ์ดํ„ฐ + ์ˆœ์„œ ๋ถˆํ•„์š”
Map<String, Customer> map = new LinkedHashMap<>(1_000_000);  // โŒ 1.5๋ฐฐ ๋ฉ”๋ชจ๋ฆฌ

ํ•ด๊ฒฐ: ์ˆœ์„œ ํ•„์š” ์—†์œผ๋ฉด HashMap.

Map<String, Customer> map = new HashMap<>(1_000_000);  // ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ

์‹ค์ˆ˜ 8: ์ •๋ ฌ ์ผ๊ด€์„ฑ ์œ„๋ฐ˜

class Cargo {
    private double weight;
    
    // โŒ Comparable ์œ„๋ฐ˜
    public int compareTo(Cargo other) {
        if (this.weight < other.weight) return -1;
        return 1;  // โš ๏ธ ๊ฐ™์„ ๋•Œ๋„ 1 ๋ฐ˜ํ™˜ โ†’ ์ผ๊ด€์„ฑ X
    }
}

์›์น™: compareTo == 0 ์ด๋ฉด equals == true ์—ฌ์•ผ ํ•จ (์ •๋ ฌ ์•ˆ์ •์„ฑ).

ํ•ด๊ฒฐ:

public int compareTo(Cargo other) {
    return Double.compare(this.weight, other.weight);
}

๐Ÿ”— 8. ์—ฐ๊ด€ ๊ฐœ๋… ๋งต

Phase 6 (๋ฐ์ดํ„ฐ ๋‹ค๋ฃจ๊ธฐ) ๋งˆ๋ฌด๋ฆฌ

[Unit 6.1: String + Constant Pool] โœ“
        โ†“
[Unit 6.2: StringBuilder vs StringBuffer] โœ“
        โ†“
[Unit 6.3: ArrayList vs LinkedList] โœ“
        โ†“
[Unit 6.4: HashMap ๋‚ด๋ถ€ ๊ตฌ์กฐ] โœ“ โ˜…โ˜…โ˜…
        โ†“
[Unit 6.5: TreeMap, LinkedHashMap] โ† ์ง€๊ธˆ (Phase 6 ์™„๋ฃŒ!)
        โ†“
[Phase 7: I/O & NIO] (์˜ˆ์ •)

์ž๋ฃŒ๊ตฌ์กฐ ์ข…ํ•ฉ

List โ”€โ”€โ”€ ArrayList โญ (Unit 6.3)
     โ”œโ”€โ”€ LinkedList
     โ””โ”€โ”€ Vector

Set  โ”€โ”€โ”€ HashSet
     โ”œโ”€โ”€ LinkedHashSet (LinkedHashMap ์˜ Set ๋ฒ„์ „)
     โ””โ”€โ”€ TreeSet (TreeMap ์˜ Set ๋ฒ„์ „)

Map  โ”€โ”€โ”€ HashMap โญ (Unit 6.4)
     โ”œโ”€โ”€ LinkedHashMap โญ (Unit 6.5)
     โ”œโ”€โ”€ TreeMap โญ (Unit 6.5)
     โ”œโ”€โ”€ ConcurrentHashMap
     โ””โ”€โ”€ Hashtable (legacy)

Queue โ”€โ”€ ArrayDeque โญ
     โ”œโ”€โ”€ LinkedList (Deque ๋„ ๊ตฌํ˜„)
     โ””โ”€โ”€ PriorityQueue (TreeMap ๊ณผ ์œ ์‚ฌ โ€” ์ •๋ ฌ ์œ ์ง€)

Red-Black Tree ์˜ ์ž๋ฐ” ํ™œ์šฉ

์‚ฌ์šฉ์ฒ˜์„ค๋ช…
TreeMapMap ์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„
TreeSetSet ์˜ ์ •๋ ฌ ๊ตฌํ˜„
HashMap (Java 8+)์ถฉ๋Œ 8๊ฐœ ์ดˆ๊ณผ ์‹œ ํŠธ๋ฆฌํ™”
LinkedHashMap (Java 8+)๊ฐ™์€ ํŠธ๋ฆฌํ™”
ConcurrentHashMap (Java 8+)๊ฐ™์€ ํŠธ๋ฆฌํ™”

โ†’ Red-Black Tree ๋Š” ์ž๋ฐ” ์ปฌ๋ ‰์…˜์˜ ํ•ต์‹ฌ.


๋‹ค๋ฅธ ์–ธ์–ด ๋น„๊ต

์–ธ์–ด์ •๋ ฌ Map์ˆœ์„œ Map
JavaTreeMapLinkedHashMap
Python(sortedcontainers)OrderedDict
JavaScript(์—†์Œ)Map (์‚ฝ์ž… ์ˆœ์„œ)
C++std::map(์—†์Œ)
C#SortedDictionary(์—†์Œ)
RustBTreeMapIndexMap (์™ธ๋ถ€)

โ†’ ์ž๋ฐ”๋Š” ๋‘ ๊ฐ€์ง€ ๋ชจ๋‘ ํ‘œ์ค€ ์ œ๊ณต โ€” ๊ฐ•์ .


๋ฉด์ ‘ ๋‹จ๊ณจ ์งˆ๋ฌธ ๋งคํ•‘

์งˆ๋ฌธ์ด Unit ์—์„œ์˜ ๋‹ต
TreeMap ์–ธ์ œ?์ •๋ ฌ/๋ฒ”์œ„ ์ฟผ๋ฆฌ
LinkedHashMap ์–ธ์ œ?์ˆœ์„œ ๋ณด์กด/LRU
LRU ์บ์‹œ ๊ตฌํ˜„?LinkedHashMap + accessOrder + removeEldestEntry
TreeMap ๋‚ด๋ถ€?Red-Black Tree
HashMap vs TreeMap?O(1) vs O(log n)

๐Ÿ“ 9. ํ•ต์‹ฌ ์š”์•ฝ โ€” 3์ค„ ์ •๋ฆฌ

1๏ธโƒฃ TreeMap = Red-Black Tree, LinkedHashMap = HashMap + ์–‘๋ฐฉํ–ฅ ๋ฆฌ์ŠคํŠธ.

TreeMap ์€ ์ •๋ ฌ๋œ ์ƒํƒœ ์œ ์ง€ โ€” ๋ชจ๋“  ์ž‘์—… O(log n), ๊ทธ๋Ÿฌ๋‚˜ ๋ฒ”์œ„ ์ฟผ๋ฆฌ/๊ทผ์ ‘ ํ‚ค ๊ฒ€์ƒ‰ ์ด ๊ฐ•์  (subMap, floorKey, ceilingKey). LinkedHashMap ์€ HashMap ์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ (O(1)) + ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ ๋กœ ์‚ฝ์ž… ์ˆœ์„œ ๋˜๋Š” ์ ‘๊ทผ ์ˆœ์„œ ์œ ์ง€. ๋ฉ”๋ชจ๋ฆฌ๋Š” HashMap ์˜ 1.5๋ฐฐ.

2๏ธโƒฃ LinkedHashMap ์˜ accessOrder=true + removeEldestEntry = LRU ์บ์‹œ 6์ค„.

accessOrder=true ๋ฉด get ์‹œ ๊ทธ ํ•ญ๋ชฉ์ด ๋งจ ๋’ค๋กœ ์ด๋™ โ†’ head ๋Š” ํ•ญ์ƒ ๊ฐ€์žฅ ์˜ค๋ž˜ ์•ˆ ์“ด ํ•ญ๋ชฉ. removeEldestEntry ์˜ค๋ฒ„๋ผ์ด๋“œ ๋กœ capacity ์ดˆ๊ณผ ์‹œ head ์ž๋™ ์ œ๊ฑฐ. ๋ชจ๋“  ์ž‘์—… O(1). ์ž๋ฐ” ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์ˆจ๊ฒจ์ง„ ๋ณด์„.

3๏ธโƒฃ ์„ ํƒ ์›์น™ โ€” ์ •๋ ฌ/๋ฒ”์œ„ โ†’ TreeMap, ์ˆœ์„œ/LRU โ†’ LinkedHashMap, ๊ทธ ์™ธ โ†’ HashMap.

TreeMap ์˜ ๊ฐ•์ : subMap, headMap, tailMap, floorKey, ceilingKey ๊ฐ™์€ NavigableMap ๋ฉ”์„œ๋“œ. LinkedHashMap ์˜ ๊ฐ•์ : ์‚ฝ์ž… ์ˆœ์„œ ๋ณด์กด (API ์‘๋‹ต JSON ์ˆœ์„œ), LRU ์บ์‹œ. HashMap ์€ ์ˆœ์„œ ๋ฌด๊ด€ ์‹œ ๊ธฐ๋ณธ. ILIC ์˜ ๊ฐ€๊ฒฉ๋Œ€๋ณ„ ํ™”๋ฌผ = TreeMap, ์šด์ž„ ์บ์‹œ = LinkedHashMap, ์ผ๋ฐ˜ ๋งคํ•‘ = HashMap. Phase 6 ์™„๋ฃŒ โ€” ์ž๋ฐ” ์ปฌ๋ ‰์…˜ ์ •๋ณต!


๐ŸŽ“ ํ•™์Šต ์ž๊ธฐ ์ ๊ฒ€

๊ธฐ๋ณธ ์ดํ•ด

  • TreeMap ์˜ Red-Black Tree ๊ธฐ๋ฐ˜ ๋™์ž‘์„ ์•ˆ๋‹ค
  • LinkedHashMap ์˜ ์–‘๋ฐฉํ–ฅ ๋ฆฌ์ŠคํŠธ ๊ตฌ์กฐ๋ฅผ ์•ˆ๋‹ค
  • accessOrder ์˜ ๋‘ ๋ชจ๋“œ๋ฅผ ์•ˆ๋‹ค (์‚ฝ์ž… ์ˆœ์„œ vs ์ ‘๊ทผ ์ˆœ์„œ)
  • NavigableMap ์˜ ๊ฐ•๋ ฅํ•œ ๋ฉ”์„œ๋“œ๋“ค์„ ์•ˆ๋‹ค

์‹ค์ „ ์ ์šฉ

  • LinkedHashMap ์œผ๋กœ LRU ์บ์‹œ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค
  • TreeMap ์˜ ๋ฒ”์œ„ ์ฟผ๋ฆฌ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค
  • ์ƒํ™ฉ์— ๋งž๋Š” Map ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค
  • Comparator ๋กœ ์ปค์Šคํ…€ ์ •๋ ฌ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค

๋ฉด์ ‘ ๋Œ€๋น„ (5๋ถ„ ๋‹ต๋ณ€)

  • "TreeMap ์–ธ์ œ ์“ฐ๋‚˜?" ๋‹ต๋ณ€ ๊ฐ€๋Šฅ
  • "LRU ์บ์‹œ ๊ตฌํ˜„?" ๋‹ต๋ณ€ ๊ฐ€๋Šฅ (LinkedHashMap)
  • "Map ๊ตฌํ˜„์ฒด ๋น„๊ต?" ๋‹ต๋ณ€ ๊ฐ€๋Šฅ
  • "Red-Black Tree?" ๊ธฐ๋ณธ ๋‹ต๋ณ€ ๊ฐ€๋Šฅ

์ž๊ธฐ ์ ๊ฒ€ ์งˆ๋ฌธ ๋‹ต๋ณ€

Q1: LRU ์บ์‹œ๋ฅผ LinkedHashMap ์œผ๋กœ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”๊ฐ€?

ํ•œ ์ค„ ๋‹ต: accessOrder=true ๋กœ ์ƒ์„ฑ ํ›„ removeEldestEntry ์˜ค๋ฒ„๋ผ์ด๋“œ. 6์ค„ ๊ตฌํ˜„, ๋ชจ๋“  ์ž‘์—… O(1).

์ƒ์„ธ ์„ค๋ช…:

1. ๊ธฐ๋ณธ ๊ตฌ์กฐ

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;
    
    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);  // โ˜… accessOrder = true
        this.capacity = capacity;
    }
    
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;  // โ˜… ์ดˆ๊ณผ ์‹œ ๊ฐ€์žฅ ์˜ค๋ž˜๋œ ์ œ๊ฑฐ
    }
}

6์ค„๋กœ LRU ์™„์„ฑ!


2. ๋™์ž‘ ๋ฉ”์ปค๋‹ˆ์ฆ˜

์ƒํƒœ 1: ์บ์‹œ capacity = 3, ๋น„์–ด์žˆ์Œ

head โ†’ null โ† tail

์ƒํƒœ 2: put("A", 1)

head โ†’ A โ† tail

์ƒํƒœ 3: put("B", 2), put("C", 3)

head โ†’ A โ†” B โ†” C โ† tail

์ƒํƒœ 4: get("A")

A ๊ฐ€ ์ ‘๊ทผ๋จ โ†’ ๋งจ ๋’ค๋กœ ์ด๋™
head โ†’ B โ†” C โ†” A โ† tail

์ƒํƒœ 5: put("D", 4) โ€” capacity ์ดˆ๊ณผ!

1. afterNodeInsertion(true) ํ˜ธ์ถœ
2. removeEldestEntry(head=B) ํ˜ธ์ถœ โ†’ size()=4 > 3 โ†’ true
3. head (B) ์ œ๊ฑฐ

head โ†’ C โ†” A โ†” D โ† tail

โ†’ ๊ฐ€์žฅ ์˜ค๋ž˜ ์•ˆ ์“ด ํ•ญ๋ชฉ (B) ์ž๋™ ์ œ๊ฑฐ.


3. ์‹œ๊ฐ„ ๋ณต์žก๋„

๋ชจ๋“  ์ž‘์—…: O(1)

  • get: HashMap ์˜ O(1) + ์–‘๋ฐฉํ–ฅ ๋ฆฌ์ŠคํŠธ ์ด๋™ O(1)
  • put: HashMap ์˜ O(1) + ๋ฆฌ์ŠคํŠธ ๋ ์ถ”๊ฐ€ O(1)
  • remove (LRU): head ์ œ๊ฑฐ O(1)

4. ์‹ค์ „ ํ™œ์šฉ

@Service
public class CustomerCache {
    private final Map<String, Customer> cache = 
        Collections.synchronizedMap(new LRUCache<>(10000));
    // 10,000 ๊ฐœ๊นŒ์ง€ ์บ์‹œ, ๊ทธ ์ด์ƒ์€ ์ž๋™ ์ œ๊ฑฐ
    
    public Customer getCustomer(String id) {
        return cache.computeIfAbsent(id, this::loadFromDb);
    }
    
    private Customer loadFromDb(String id) {
        return customerRepository.findById(id).orElse(null);
    }
}

5. ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ๊ณ ๋ ค

LinkedHashMap ์€ Thread-Safe X.

ํ•ด๊ฒฐ 1: synchronizedMap

Map<K, V> cache = Collections.synchronizedMap(new LRUCache<>(100));

ํ•ด๊ฒฐ 2: Caffeine ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (๊ถŒ์žฅ)

Cache<K, V> cache = Caffeine.newBuilder()
    .maximumSize(100)
    .build();

ํ•ด๊ฒฐ 3: ConcurrentHashMap + ๋ณ„๋„ LRU ๋กœ์ง (๋ณต์žก)


6. ๊ฒฐ๋ก 

"LinkedHashMap ์˜ accessOrder=true + removeEldestEntry ์˜ค๋ฒ„๋ผ์ด๋“œ = ์™„๋ฒฝํ•œ LRU.
6์ค„๋กœ ๋ชจ๋“  ์ž‘์—… O(1), ๋ฉ”๋ชจ๋ฆฌ ์ผ์ •.
์ž๋ฐ” ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์ˆจ๊ฒจ์ง„ ๋ณด์„ โ€” ์•Œ๋ฉด ์‹œ๋‹ˆ์–ด, ๋ชจ๋ฅด๋ฉด ์ง์ ‘ ๊ตฌํ˜„ํ•ด O(n).
๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ๋Š” synchronizedMap ๋˜๋Š” Caffeine ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ™œ์šฉ."


Q2: TreeMap ์˜ subMap ๊ณผ HashMap ์œผ๋กœ ๊ฐ™์€ ์ž‘์—… ๊ตฌํ˜„ ์‹œ ์‹œ๊ฐ„ ์ฐจ์ด๋Š”?

ํ•œ ์ค„ ๋‹ต: TreeMap ์˜ subMap = O(log n + k), HashMap ์œผ๋กœ ๊ฐ™์€ ์ž‘์—… = O(n). ๋ฐ์ดํ„ฐ ํฌ๋ฉด ์ˆ˜์ฒœ ๋ฐฐ ์ฐจ์ด.

์ƒ์„ธ ์„ค๋ช…:

1. ์‹œ๋‚˜๋ฆฌ์˜ค โ€” ๊ฐ€๊ฒฉ๋Œ€ ๊ฒ€์ƒ‰

// 100๋งŒ ๊ฐœ ํ™”๋ฌผ, 5,000~10,000์› ๋ฒ”์œ„ ๊ฒ€์ƒ‰
NavigableMap<Integer, List<Cargo>> tree = new TreeMap<>();
Map<Integer, List<Cargo>> hash = new HashMap<>();
// ๋ฐ์ดํ„ฐ ์ฑ„์šฐ๊ธฐ ...

// TreeMap
List<Cargo> result1 = tree.subMap(5000, 10001).values().stream()
    .flatMap(List::stream).collect(Collectors.toList());

// HashMap
List<Cargo> result2 = hash.entrySet().stream()
    .filter(e -> e.getKey() >= 5000 && e.getKey() <= 10000)
    .flatMap(e -> e.getValue().stream())
    .collect(Collectors.toList());

2. ์‹œ๊ฐ„ ๋ถ„์„

TreeMap.subMap:
1. ์‹œ์ž‘ ํ‚ค (5000) ์œ„์น˜ ์ฐพ๊ธฐ: O(log n) โ‰ˆ 20๋ฒˆ ๋น„๊ต (100๋งŒ)
2. ๋ ํ‚ค (10001) ์œ„์น˜ ์ฐพ๊ธฐ: O(log n) โ‰ˆ 20๋ฒˆ ๋น„๊ต
3. ์‚ฌ์ด์˜ entry ์ˆœํšŒ: O(k) (์˜ˆ: 100๊ฐœ)
4. ํ•ฉ๊ณ„: O(log n + k) โ‰ˆ 140๋ฒˆ ์ž‘์—…

HashMap ํ•„ํ„ฐ๋ง:
1. ๋ชจ๋“  entry ์ˆœํšŒ: O(n) = 100๋งŒ ๋ฒˆ
2. ๊ฐ๊ฐ ๋ฒ”์œ„ ๊ฒ€์‚ฌ: 100๋งŒ ๋ฒˆ ๋น„๊ต
3. ํ•ฉ๊ณ„: O(n) = 100๋งŒ ๋ฒˆ

์ฐจ์ด: ์•ฝ 7,000๋ฐฐ.


3. ์‹ค์ธก

public class RangeQueryBenchmark {
    public static void main(String[] args) {
        int N = 1_000_000;
        NavigableMap<Integer, String> tree = new TreeMap<>();
        Map<Integer, String> hash = new HashMap<>();
        
        for (int i = 0; i < N; i++) {
            tree.put(i, "value-" + i);
            hash.put(i, "value-" + i);
        }
        
        // TreeMap subMap
        long start = System.nanoTime();
        Map<Integer, String> sub = tree.subMap(500000, 500100);
        int size1 = sub.size();
        long treeTime = System.nanoTime() - start;
        
        // HashMap ํ•„ํ„ฐ๋ง
        start = System.nanoTime();
        long count = hash.entrySet().stream()
            .filter(e -> e.getKey() >= 500000 && e.getKey() < 500100)
            .count();
        long hashTime = System.nanoTime() - start;
        
        System.out.println("TreeMap subMap: " + treeTime + " ns");
        // ์•ฝ 10,000 ns = 10 ฮผs
        
        System.out.println("HashMap filter: " + hashTime + " ns");
        // ์•ฝ 30,000,000 ns = 30 ms (3,000๋ฐฐ)
    }
}

4. ๋” ํฐ ๋ฒ”์œ„์—์„œ

๋ฐ์ดํ„ฐ ์ˆ˜TreeMap subMapHashMap filter์ฐจ์ด
1,0001 ฮผs50 ฮผs50๋ฐฐ
10,0002 ฮผs500 ฮผs250๋ฐฐ
100,0005 ฮผs5 ms1,000๋ฐฐ
1,000,00010 ฮผs30 ms3,000๋ฐฐ
10,000,00015 ฮผs300 ms20,000๋ฐฐ

โ†’ ๋ฐ์ดํ„ฐ ํด์ˆ˜๋ก ์ฐจ์ด ํญ์ฆ.


5. ๊ฒฐ๋ก 

"๋ฒ”์œ„ ์ฟผ๋ฆฌ๋Š” ์ž๋ฃŒ๊ตฌ์กฐ ์„ ํƒ์˜ ๊ฒฐ์ •์  ์ฐจ์ด.
TreeMap ์€ ์ •๋ ฌ๋œ ๊ตฌ์กฐ ๋•๋ถ„์— O(log n + k) โ€” ๋ฐ์ดํ„ฐ๊ฐ€ 100๋งŒ ๊ฐœ์—ฌ๋„ ๊ฑฐ์˜ ์ฆ‰์‹œ.
HashMap ์€ ์ •๋ ฌ X โ†’ O(n) ํ•„ํ„ฐ๋ง ๊ฐ•์ œ โ€” ๋งค๋ฒˆ ์ „์ฒด ์ˆœํšŒ.
ILIC ์˜ ๊ฐ€๊ฒฉ๋Œ€๋ณ„ ํ™”๋ฌผ, ์‹œ๊ฐ„๋Œ€๋ณ„ ์กฐํšŒ, ๋“ฑ๊ธ‰๋ณ„ ๊ฒ€์ƒ‰ ๋“ฑ ๋ฒ”์œ„ ์ฟผ๋ฆฌ๊ฐ€ ๋นˆ๋ฒˆํ•˜๋ฉด TreeMap ์ •๋‹ต.
์‹œ๋‹ˆ์–ด ์ฐจ๋ณ„ํ™”: subMap ์˜ view ํŠน์„ฑ, NavigableMap ์˜ ๊ฐ•๋ ฅํ•œ ๋ฉ”์„œ๋“œ ํ™œ์šฉ."


๋‹ค์Œ ํ•™์Šต์œผ๋กœ

  • Phase 6 ์™„๋ฃŒ ๐ŸŽ‰ (Unit 6.1-6.5 ๋ชจ๋‘ ์™„๋ฃŒ)
  • Phase 7 โ€” I/O & NIO ํ•™์Šต ์ค€๋น„ ์™„๋ฃŒ
  • ์ปฌ๋ ‰์…˜ ์ •์ ์—์„œ ์ž๋ฐ” ์‹œ๋‹ˆ์–ด ๋‹ต๋ณ€ ๊ฐ€๋Šฅ
profile
Software Developer

0๊ฐœ์˜ ๋Œ“๊ธ€