๐ŸŽฏ1์ฃผ์ฐจ Unit 6.4 โ€” HashMap ๋‚ด๋ถ€ ๊ตฌ์กฐ

Psjยท์–ด์ œ

F-lab

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

๐ŸŽฏ Unit 6.4 โ€” HashMap ๋‚ด๋ถ€ ๊ตฌ์กฐ โ˜…โ˜…โ˜…

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

์„ ์ˆ˜ ์ง€์‹: Phase 4-5 (๋ฉ”๋ชจ๋ฆฌ, GC), Unit 6.1-6.3 (String, ์ปฌ๋ ‰์…˜ ๊ธฐ์ดˆ)
๋‹ค์Œ Unit: 6.5 โ€” TreeMap, LinkedHashMap

์ด Unit์˜ ์˜๋ฏธ: ์ž๋ฐ” ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐ€์žฅ ์ž์ฃผ ์“ฐ๋Š” ์ž๋ฃŒ๊ตฌ์กฐ.
๋ฉด์ ‘ ์ตœ๋‹จ๊ณจ ์ค‘์˜ ์ตœ๋‹จ๊ณจ (โ˜…โ˜…โ˜…) โ€” hashCode + equals, ํ•ด์‹œ ์ถฉ๋Œ, Treeification.
๋ชจ๋“  ์บ์‹œ, ๋ชจ๋“  ID ๋งคํ•‘, ๋ชจ๋“  ๊ทธ๋ฃนํ•‘์˜ ๊ธฐ๋ฐ˜.


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

HashMap = "์šฐ์ฒด๊ตญ ์‚ฌ๋ฌผํ•จ"

๋Œ€ํ˜• ์šฐ์ฒด๊ตญ์˜ ์‚ฌ๋ฌผํ•จ ์‹œ์Šคํ…œ์„ ์ƒ์ƒํ•ด๋ณด์„ธ์š”.

์‹œ๋‚˜๋ฆฌ์˜ค 1 โ€” ์‚ฌ๋ฌผํ•จ ์—†๋Š” ์šฐ์ฒด๊ตญ (List ๋งŒ ์žˆ๋Š” ์„ธ์ƒ)

ํŽธ์ง€ ๋”๋ฏธ:
[Alice ํŽธ์ง€][Bob ํŽธ์ง€][Charlie ํŽธ์ง€][David ํŽธ์ง€]...
  • "Charlie ํŽธ์ง€ ๊ฐ€์ ธ์™€" โ†’ ๋ชจ๋“  ํŽธ์ง€๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ํ™•์ธ
  • 1๋งŒ ๋ช…์ด๋ฉด โ†’ ํ‰๊ท  5์ฒœ ๋ฒˆ ํ™•์ธ
  • โ†’ O(n) ์‹œ๊ฐ„

์‹œ๋‚˜๋ฆฌ์˜ค 2 โ€” ์‚ฌ๋ฌผํ•จ ์žˆ๋Š” ์šฐ์ฒด๊ตญ (HashMap)

์‚ฌ๋ฌผํ•จ:
[101] [102] [103] [104] [105] ... [999]
 โ†“     โ†“     โ†“     โ†“
Alice  -    Bob   Charlie
ํŽธ์ง€       ํŽธ์ง€   ํŽธ์ง€

๊ทœ์น™: "์ด๋ฆ„ โ†’ ์‚ฌ๋ฌผํ•จ ๋ฒˆํ˜ธ" ๋ณ€ํ™˜ ํ•จ์ˆ˜ (ํ•ด์‹œ ํ•จ์ˆ˜)

  • "Alice" โ†’ hash("Alice") % 1000 = 101
  • "Bob" โ†’ hash("Bob") % 1000 = 103
  • "Charlie" โ†’ hash("Charlie") % 1000 = 104

์ฐพ๊ธฐ:

  • "Charlie ํŽธ์ง€" โ†’ hash ๊ณ„์‚ฐ โ†’ 104๋ฒˆ ์‚ฌ๋ฌผํ•จ โ†’ ์ฆ‰์‹œ!
  • โ†’ O(1) ์‹œ๊ฐ„

์‹œ๋‚˜๋ฆฌ์˜ค 3 โ€” ์‚ฌ๋ฌผํ•จ ์ถฉ๋Œ (ํ•ด์‹œ ์ถฉ๋Œ)

"Alice"  โ†’ 101๋ฒˆ
"David"  โ†’ 101๋ฒˆ (์šฐ์—ฐํžˆ ๊ฐ™์€ ๋ฒˆํ˜ธ!)

ํ•ด๊ฒฐ: ํ•œ ์‚ฌ๋ฌผํ•จ์— ์—ฌ๋Ÿฌ ํŽธ์ง€ ๋ณด๊ด€ ๊ฐ€๋Šฅ

์‚ฌ๋ฌผํ•จ 101:
  [Alice ํŽธ์ง€] โ†’ [David ํŽธ์ง€]
        โ†‘           โ†‘
     ์ด๋ฆ„ ํ™•์ธ ํ›„ ์ •ํ™•ํ•œ ํŽธ์ง€ ์„ ํƒ

โ†’ "Alice" ์ฐพ๊ธฐ: ์‚ฌ๋ฌผํ•จ 101 โ†’ Alice ํŽธ์ง€ (์ด๋ฆ„ ๋น„๊ต)


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

"HashMap = ๋ฐฐ์—ด (์‚ฌ๋ฌผํ•จ) + ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ/ํŠธ๋ฆฌ (์ถฉ๋Œ ํ•ด๊ฒฐ) โ€” O(1) ํ‰๊ท , O(log n) ์ตœ์•…."

๋น„์œ  ์ •๋ฆฌ:

๋น„์œ HashMap ๊ฐœ๋…
์‚ฌ๋ฌผํ•จ ๋ฒˆํ˜ธ๋ฐฐ์—ด ์ธ๋ฑ์Šค (bucket)
์ด๋ฆ„ โ†’ ๋ฒˆํ˜ธ ๋ณ€ํ™˜hashCode() ๋ฉ”์„œ๋“œ
๊ฐ™์€ ์‚ฌ๋ฌผํ•จ์— ์—ฌ๋Ÿฌ ํŽธ์ง€ํ•ด์‹œ ์ถฉ๋Œ (Chaining)
์‚ฌ๋ฌผํ•จ ์•ˆ์—์„œ ์ด๋ฆ„ ๋น„๊ตequals() ๋ฉ”์„œ๋“œ
์‚ฌ๋ฌผํ•จ ๋Š˜๋ฆฌ๊ธฐRehashing (capacity ํ™•์žฅ)

๋˜ ๋‹ค๋ฅธ ๋น„์œ  โ€” "๋„์„œ๊ด€ ์‹ญ์ง„๋ถ„๋ฅ˜๋ฒ•"

์ฑ… = key, ๋ถ„๋ฅ˜ ๋ฒˆํ˜ธ = hash, ์ฑ…์žฅ = bucket

"์ž๋ฐ”์˜ ์ •์„" โ†’ 005 (์ปดํ“จํ„ฐ ๋ถ„์•ผ)
"์ด์ƒํ•œ ๋‚˜๋ผ์˜ ์•จ๋ฆฌ์Šค" โ†’ 843 (์˜๋ฌธํ•™)
"์ˆ˜ํ•™์˜ ์ •์„" โ†’ 510 (์ˆ˜ํ•™)

์ฐพ๊ธฐ:
1. ์ฑ… ์ œ๋ชฉ โ†’ ๋ถ„๋ฅ˜ ๋ฒˆํ˜ธ ๊ณ„์‚ฐ (hashCode)
2. ๋ถ„๋ฅ˜ ๋ฒˆํ˜ธ โ†’ ์ฑ…์žฅ ์œ„์น˜ (์ธ๋ฑ์Šค)
3. ์ฑ…์žฅ์—์„œ ์ฑ… ์ฐพ๊ธฐ (equals ๋กœ ์ •ํ™•ํžˆ ๋น„๊ต)

โ†’ ๋น ๋ฅด๊ฒŒ ์ฑ… ์ฐพ๊ธฐ.


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

"์ˆ˜์‹ญ๋งŒ ๋ฐ์ดํ„ฐ์—์„œ ์ฆ‰์‹œ ์ฐพ๊ณ  ์‹ถ๋‹ค" โ€” ํ•ด์‹œ ํ…Œ์ด๋ธ”์˜ ๋“ฑ์žฅ

1950๋…„๋Œ€ ์ด์ „ โ€” ๊ฒ€์ƒ‰์€ ๋А๋ ธ๋‹ค

๋ฐ์ดํ„ฐ ๊ฒ€์ƒ‰์˜ ์˜ต์…˜:

  • ์ˆœ์ฐจ ํƒ์ƒ‰: O(n)
  • ์ •๋ ฌ ํ›„ ์ด์ง„ ํƒ์ƒ‰: O(log n)
  • โ†’ ์—ฌ์ „ํžˆ ๋А๋ฆผ

1953๋…„ โ€” ํ•ด์‹œ ํ…Œ์ด๋ธ”์˜ ๋ฐœ๋ช…

IBM ์˜ Hans Peter Luhn ์ด ์ œ์•ˆ:

  • ํ•ด์‹œ ํ•จ์ˆ˜ ๋กœ ํ‚ค๋ฅผ ์ธ๋ฑ์Šค๋กœ ๋ณ€ํ™˜
  • ๋ฐฐ์—ด์— ์ง์ ‘ ์ €์žฅ
  • โ†’ O(1) ํ‰๊ท  ์‹œ๊ฐ„

ํ•ต์‹ฌ ์•„์ด๋””์–ด:

key โ†’ hash(key) โ†’ array[hash(key)] = value

์ž๋ฐ”์˜ ๋‹ต โ€” HashMap (Java 1.2, 1998)

public class HashMap<K, V> {
    transient Node<K,V>[] table;  // ๋ฐฐ์—ด (bucket)
    transient int size;           // ์š”์†Œ ์ˆ˜
    
    static class Node<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;  // ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ
    }
}

ํ•ต์‹ฌ ๊ตฌ์„ฑ:
1. ๋ฐฐ์—ด (table): ๊ณ ์ • ์œ„์น˜ ์ ‘๊ทผ
2. ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ (Node.next): ์ถฉ๋Œ ์ฒ˜๋ฆฌ
3. ํŠธ๋ฆฌ (Java 8+): ์ถฉ๋Œ ๋„ˆ๋ฌด ๋งŽ์„ ๋•Œ


ํ•ต์‹ฌ ๋„๊ตฌ โ€” hashCode + equals

hashCode ์˜ ์—ญํ• 

public class Object {
    public native int hashCode();
    public boolean equals(Object obj) {
        return (this == obj);
    }
}

์šฉ๋„: ๊ฐ์ฒด๋ฅผ ์ •์ˆ˜ ์ธ๋ฑ์Šค ๋กœ ๋ณ€ํ™˜.

"Alice".hashCode()  // 63350368
"Bob".hashCode()    // 66486

equals ์˜ ์—ญํ• 

๊ฐ™์€ hashCode ์ธ ๋‘ ๊ฐ์ฒด๊ฐ€ ์ง„์งœ ๊ฐ™์€๊ฐ€? ๊ฒ€์ฆ.

String a = "Alice";
String b = "Alice";
a.hashCode() == b.hashCode();  // true (๋‹น์—ฐ)
a.equals(b);                   // true (๊ฐ’ ๋น„๊ต)

์ž๋ฐ” ์ปจ๋ฒค์…˜ โ€” equals + hashCode ๊ณ„์•ฝ

๊ทœ์น™ 1: a.equals(b) ๊ฐ€ true ๋ผ๋ฉด, a.hashCode() == b.hashCode() ๋„ ๋ฐ˜๋“œ์‹œ true.

๊ทœ์น™ 2: a.hashCode() == b.hashCode() ๋ผ๊ณ  ํ•ด์„œ a.equals(b) ๊ฐ€ true ์ธ ๊ฑด ์•„๋‹˜ (์ถฉ๋Œ ๊ฐ€๋Šฅ).

์ฆ‰:

  • equals โ†’ hashCode (ํ•œ ๋ฐฉํ–ฅ๋งŒ ๋ณด์žฅ)
  • equals ์˜ค๋ฒ„๋ผ์ด๋“œ ์‹œ โ†’ hashCode ๋„ ๋ฐ˜๋“œ์‹œ ์˜ค๋ฒ„๋ผ์ด๋“œ

์œ„๋ฐ˜ ์‹œ โ€” HashMap ๋ง๊ฐ€์ง

// โŒ equals ๋งŒ ์˜ค๋ฒ„๋ผ์ด๋“œ, hashCode ์•ˆ ํ•จ
public class Customer {
    private String name;
    
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Customer)) return false;
        return name.equals(((Customer) o).name);
    }
    // hashCode() ์˜ค๋ฒ„๋ผ์ด๋“œ X โ†’ Object ์˜ ๊ธฐ๋ณธ (๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ ๊ธฐ๋ฐ˜)
}

// ์‚ฌ์šฉ
Map<Customer, Integer> map = new HashMap<>();
map.put(new Customer("Alice"), 100);
map.get(new Customer("Alice"));  // null! (๋‹ค๋ฅธ hashCode)

โ†’ HashMap ์˜ ์ •ํ™•์„ฑ์ด ๊นจ์ง.


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

"HashMap ์˜ ๋งˆ๋ฒ• = hashCode (์œ„์น˜ ๊ฒฐ์ •) + equals (์ •ํ™•์„ฑ ๊ฒ€์ฆ) + ์ ์ ˆํ•œ Bucket ํฌ๊ธฐ."

์…‹ ์ค‘ ํ•˜๋‚˜๋งŒ ์–ด๊ธ‹๋‚˜๋„ O(n) ์œผ๋กœ ๋–จ์–ด์ง€๊ฑฐ๋‚˜ ์ •ํ™•์„ฑ์ด ๊นจ์ง„๋‹ค. ์ž๋ฐ”์˜ Object ๊ฐ€ hashCode/equals ๋‘ ๋ฉ”์„œ๋“œ๋ฅผ ๊ธฐ๋ณธ ์ œ๊ณตํ•˜๋Š” ์ด์œ  โ€” ๋ชจ๋“  ๊ฐ์ฒด๋Š” HashMap ์˜ ํ‚ค๊ฐ€ ๋  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค๋Š” ์ž๋ฐ”์˜ ์ฒ ํ•™.


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

์‹œ๋‚˜๋ฆฌ์˜ค 1: ILIC ์˜ ๊ณ ๊ฐ ์บ์‹œ โ€” equals๋งŒ ์˜ค๋ฒ„๋ผ์ด๋“œ

// โŒ ์šด์˜ ์‚ฌ๊ณ  ์ฝ”๋“œ
public class Customer {
    private String customerId;
    private String name;
    
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Customer)) return false;
        return customerId.equals(((Customer) o).customerId);
    }
    
    // hashCode() ์˜ค๋ฒ„๋ผ์ด๋“œ X
}

@Service
public class CustomerCacheService {
    private Map<Customer, CustomerInfo> cache = new HashMap<>();
    
    public CustomerInfo get(Customer key) {
        return cache.get(key);  // โŒ ํ•ญ์ƒ null ๋ฐ˜ํ™˜!
    }
}

๋ฌธ์ œ:

  • DB ์—์„œ ๋งค๋ฒˆ ์ƒˆ Customer ๊ฐ์ฒด ์ƒ์„ฑ
  • equals ๋Š” customerId ๋น„๊ต โ†’ true
  • ๊ทธ๋Ÿฌ๋‚˜ hashCode ๊ฐ€ ๋‹ค๋ฆ„ โ†’ ๋‹ค๋ฅธ bucket ๊ฒ€์ƒ‰
  • โ†’ ์บ์‹œ ํžˆํŠธ์œจ 0%

์ฆ์ƒ:

  • ์บ์‹œ ์•ˆ ๋จนํž˜ โ†’ DB ๋ถ€ํ•˜ โ†‘
  • API ์‘๋‹ต ์‹œ๊ฐ„ ๋А๋ ค์ง
  • ์™œ์ธ์ง€ ๋ชจ๋ฆ„

ํ•ด๊ฒฐ:

@Override
public int hashCode() {
    return Objects.hash(customerId);
}

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

Q: "HashMap ์˜ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”"
A: "์Œ... ํ‚ค ๋ฐธ๋ฅ˜ ์ €์žฅํ•˜๋Š” ์ž๋ฃŒ๊ตฌ์กฐ์—์š”"  โŒ

์‹œ๋‹ˆ์–ด ๋‹ต๋ณ€ (์ด Unit ์˜ ๋ชฉํ‘œ):
1. ๋ฐฐ์—ด (bucket) + ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ + ํŠธ๋ฆฌ (Java 8+)
2. hashCode() ๋กœ ์œ„์น˜ ๊ณ„์‚ฐ, equals() ๋กœ ์ •ํ™•์„ฑ ๊ฒ€์ฆ
3. Load Factor (0.75) ๋„๋‹ฌ ์‹œ Rehashing (2๋ฐฐ ํ™•์žฅ)
4. TREEIFY_THRESHOLD = 8 โ€” ํ•œ bucket ์— 8๊ฐœ ์ด์ƒ์ด๋ฉด Red-Black Tree ๋กœ ์ „ํ™˜
5. capacity ๋Š” 2์˜ ๊ฑฐ๋“ญ์ œ๊ณฑ โ€” ๋น„ํŠธ ์—ฐ์‚ฐ ์ตœ์ ํ™” (hash & (n-1))


์‹œ๋‚˜๋ฆฌ์˜ค 3: ๊ฐ€๋ณ€ ๊ฐ์ฒด๋ฅผ ํ‚ค๋กœ ์‚ฌ์šฉ

// โŒ ์œ„ํ—˜
List<String> key = new ArrayList<>(List.of("A", "B"));
Map<List<String>, Integer> map = new HashMap<>();
map.put(key, 100);

// ํ‚ค ์ˆ˜์ •
key.add("C");

// ์กฐํšŒ
map.get(key);  // null! (hashCode ๋ณ€๊ฒฝ๋จ)

๋ฌธ์ œ:

  • key ์˜ hashCode ๊ฐ€ ๋ณ€๊ฒฝ๋จ
  • HashMap ์€ ๋ณ€๊ฒฝ๋œ hashCode ๋กœ ๋‹ค๋ฅธ bucket ๊ฒ€์ƒ‰
  • ์›๋ž˜ ๋“ค์–ด์žˆ๋˜ ์œ„์น˜๋Š” ๋ชป ์ฐพ์Œ โ†’ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜

ํ•ด๊ฒฐ: ๋ถˆ๋ณ€ ๊ฐ์ฒด๋ฅผ ํ‚ค๋กœ ์‚ฌ์šฉ (String, Integer, ๋ถˆ๋ณ€ ํด๋ž˜์Šค).


์‹œ๋‚˜๋ฆฌ์˜ค 4: ํ•ด์‹œ ์ถฉ๋Œ ๊ณต๊ฒฉ (HashDoS)

// ๋ชจ๋“  ํ‚ค๊ฐ€ ๊ฐ™์€ hashCode ๋ฐ˜ํ™˜
public class BadKey {
    private int id;
    
    @Override
    public int hashCode() {
        return 0;  // โŒ ๋ชจ๋‘ ๊ฐ™์€ hash
    }
    
    @Override
    public boolean equals(Object o) {
        return ((BadKey) o).id == id;
    }
}

// ์‚ฌ์šฉ
Map<BadKey, String> map = new HashMap<>();
for (int i = 0; i < 100000; i++) {
    map.put(new BadKey(i), "value");
}

// ๋ชจ๋‘ ๊ฐ™์€ bucket ์— ๋“ค์–ด๊ฐ โ†’ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ ๊ธธ์ด 10๋งŒ!
// O(1) โ†’ O(n)

๊ณผ๊ฑฐ (Java 7):

  • ๋ชจ๋“  ์ถฉ๋Œ โ†’ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ
  • ๊ฒ€์ƒ‰ O(n)
  • HashDoS ๊ณต๊ฒฉ ๊ฐ€๋Šฅ

Java 8+ ์˜ ๋‹ต:

  • 8๊ฐœ ์ดˆ๊ณผ ์‹œ โ†’ Red-Black Tree ๋กœ ์ „ํ™˜
  • ๊ฒ€์ƒ‰ O(log n)
  • HashDoS ์™„ํ™”

์‹œ๋‚˜๋ฆฌ์˜ค 5: ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ์—์„œ HashMap

@Service
public class GlobalCacheService {
    private Map<String, String> cache = new HashMap<>();  // โŒ ์œ„ํ—˜
    
    public void put(String k, String v) {
        cache.put(k, v);  // ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ ๋™์‹œ ํ˜ธ์ถœ โ†’ ๋ฐ์ดํ„ฐ ์†์ƒ
    }
}

๋ฌธ์ œ (ํŠนํžˆ Java 7 ์ด์ „):

  • Rehashing ์ค‘ ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ ๋™์‹œ ์ง„์ž…
  • ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์ˆœํ™˜ ์œผ๋กœ ๋ณ€ํ™˜๋  ์ˆ˜ ์žˆ์Œ
  • ๋‹ค์Œ get() โ†’ ๋ฌดํ•œ ๋ฃจํ”„ + CPU 100%

ํ•ด๊ฒฐ:

private Map<String, String> cache = new ConcurrentHashMap<>();  // โœ“

์‹œ๋‚˜๋ฆฌ์˜ค 6: ILIC ์˜ ๋“ฑ๊ธ‰๋ณ„ ์นด์šดํŠธ

// โŒ ๋น„ํšจ์œจ
List<Customer> customers = ...;
Map<String, Integer> gradeCounts = new HashMap<>();

for (Customer c : customers) {
    String grade = c.getGrade();
    if (gradeCounts.containsKey(grade)) {
        gradeCounts.put(grade, gradeCounts.get(grade) + 1);
        // โŒ 3๋ฒˆ์˜ hashCode ๊ณ„์‚ฐ + 2๋ฒˆ์˜ equals
    } else {
        gradeCounts.put(grade, 1);
    }
}

๋ฌธ์ œ: ๋งค ๋ฐ˜๋ณต๋งˆ๋‹ค 3๋ฒˆ์˜ ํ•ด์‹œ ์—ฐ์‚ฐ.

ํ•ด๊ฒฐ 1: getOrDefault

gradeCounts.put(grade, gradeCounts.getOrDefault(grade, 0) + 1);
// 2๋ฒˆ์˜ ํ•ด์‹œ ์—ฐ์‚ฐ

ํ•ด๊ฒฐ 2: merge (Java 8+)

gradeCounts.merge(grade, 1, Integer::sum);
// 1๋ฒˆ์˜ ํ•ด์‹œ ์—ฐ์‚ฐ

ํ•ด๊ฒฐ 3: Stream

Map<String, Long> counts = customers.stream()
    .collect(Collectors.groupingBy(
        Customer::getGrade,
        Collectors.counting()));

์˜ํ–ฅ ์ •๋ฆฌ

์‹œ๋‚˜๋ฆฌ์˜ค๋ชจ๋ฅด๋ฉด์•Œ๋ฉด
equals/hashCode์บ์‹œ ์•ˆ ๋จนํž˜์ •ํ™•ํ•œ ๋™์ž‘
๋ฉด์ ‘ ๋‹ต๋ณ€ํ‘œ๋ฉด์ ์‹œ๋‹ˆ์–ด
๊ฐ€๋ณ€ ํ‚ค๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ถˆ๋ณ€ ํ‚ค ์‚ฌ์šฉ
ํ•ด์‹œ ์ถฉ๋Œ ๊ณต๊ฒฉHashDoSTree ๋กœ ์™„ํ™”
๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ๋ฌดํ•œ ๋ฃจํ”„ConcurrentHashMap
ILIC ํ†ต๊ณ„O(nยฒ) ๊ฐ€๋ŠฅO(n)

โ†’ HashMap ์ดํ•ด๋Š” ์ž๋ฐ” ์‹œ๋‹ˆ์–ด์˜ ํ•ต์‹ฌ ์—ญ๋Ÿ‰.


โœ… 4. ํ•ด๊ฒฐ์ฑ… โ€” HashMap ์˜ ์ •ํ™•ํ•œ ์ดํ•ด

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

Map<String, Customer> map = new HashMap<>();
map.put("alice", new Customer("Alice"));
map.put("bob", new Customer("Bob"));

Customer alice = map.get("alice");  // O(1)
boolean has = map.containsKey("alice");  // O(1)
map.remove("alice");  // O(1)

// ์ˆœํšŒ
for (Map.Entry<String, Customer> entry : map.entrySet()) {
    String key = entry.getKey();
    Customer value = entry.getValue();
}

์ฃผ์š” ๋ฉ”์„œ๋“œ์™€ ์‹œ๊ฐ„ ๋ณต์žก๋„

HashMap<String, Integer> map = new HashMap<>();

// === ์ถ”๊ฐ€ ===
map.put("A", 1);              // O(1) ํ‰๊ท 
map.putIfAbsent("A", 100);    // ์—†์œผ๋ฉด ์ถ”๊ฐ€ โ€” O(1)
map.computeIfAbsent("B", k -> 2);  // ์—†์œผ๋ฉด ๋žŒ๋‹ค๋กœ ๊ณ„์‚ฐ
map.merge("A", 1, Integer::sum);   // ์žˆ์œผ๋ฉด ํ•ฉ์น˜๊ณ , ์—†์œผ๋ฉด ์ถ”๊ฐ€

// === ์กฐํšŒ ===
map.get("A");                 // O(1) ํ‰๊ท , O(log n) ์ตœ์•…
map.getOrDefault("X", 0);     // ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’
map.containsKey("A");         // O(1)
map.containsValue(1);         // O(n) โ€” ๋ชจ๋“  value ์ˆœํšŒ

// === ์‚ญ์ œ ===
map.remove("A");              // O(1)
map.remove("A", 1);           // value ๋„ ์ผ์น˜ํ•  ๋•Œ๋งŒ

// === ์ •๋ณด ===
map.size();
map.isEmpty();
map.clear();

// === ์ˆœํšŒ (์š”์†Œ ์ˆœ์„œ๋Š” ๋ณด์žฅ X) ===
map.keySet();      // Set<K>
map.values();      // Collection<V>
map.entrySet();    // Set<Map.Entry<K,V>>

// === Java 8+ ๋žŒ๋‹ค ===
map.forEach((k, v) -> System.out.println(k + "=" + v));
map.replaceAll((k, v) -> v * 2);

๋น„๊ต ํ‘œ โญโญ (Map ๊ตฌํ˜„์ฒด ์„ ํƒ)

ํ•ญ๋ชฉHashMapHashtableConcurrentHashMapLinkedHashMapTreeMap
์ˆœ์„œ์—†์Œ์—†์Œ์—†์Œ์‚ฝ์ž… ์ˆœ์„œ์ •๋ ฌ
null keyโœ“โœ—โœ—โœ“โœ—
null valueโœ“โœ—โœ—โœ“โœ“
Thread-Safeโœ—โœ“ (๋‚ก์Œ)โœ“ โญโœ—โœ—
์‹œ๊ฐ„ ๋ณต์žก๋„O(1)O(1)O(1)O(1)O(log n)
๊ถŒ์žฅ๋„๋‹จ์ผ โญโŒ Legacy๋ฉ€ํ‹ฐ โญ์ˆœ์„œ ํ•„์š”์ •๋ ฌ ํ•„์š”

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

Map ์ด ํ•„์š”ํ•˜๋‹ค
        โ†“
์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•œ๊ฐ€?
โ”œโ”€โ”€ ์ •๋ ฌ๋œ ์ˆœ์„œ โ†’ TreeMap (Unit 6.5)
โ”œโ”€โ”€ ์‚ฝ์ž… ์ˆœ์„œ โ†’ LinkedHashMap (Unit 6.5)
โ””โ”€โ”€ ๋ฌด๊ด€ โ†’ ๋‹ค์Œ ๋‹จ๊ณ„
        โ†“
๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ?
โ”œโ”€โ”€ YES โ†’ ConcurrentHashMap โญ
โ””โ”€โ”€ NO  โ†’ HashMap โญ

90% ์ด์ƒ์˜ ๊ฒฝ์šฐ โ†’ HashMap ๋˜๋Š” ConcurrentHashMap.


์ดˆ๊ธฐ capacity ์™€ load factor

// ๊ธฐ๋ณธ
new HashMap<>();  // capacity 16, loadFactor 0.75

// ํฐ ๋ฐ์ดํ„ฐ
new HashMap<>(1024);  // capacity 1024

// ์„ธ๋ฐ€ ์กฐ์ •
new HashMap<>(1024, 0.6f);  // capacity 1024, loadFactor 0.6

๊ณ„์‚ฐ:

  • 100๋งŒ ๊ฐœ ์ €์žฅ ์˜ˆ์ƒ
  • loadFactor 0.75
  • ํ•„์š” capacity: 1,000,000 / 0.75 โ‰ˆ 1,333,334
  • 2์˜ ๊ฑฐ๋“ญ์ œ๊ณฑ: 2,097,152 (2ยฒยน)
new HashMap<>(2_097_152);  // ๋ฏธ๋ฆฌ ํ• ๋‹น โ†’ rehashing ํšŒํ”ผ

Object ์˜ equals + hashCode ์˜ค๋ฒ„๋ผ์ด๋“œ โ€” ํ‘œ์ค€ ํŒจํ„ด

public class Customer {
    private String customerId;
    private String name;
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Customer)) return false;
        Customer c = (Customer) o;
        return Objects.equals(customerId, c.customerId);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(customerId);  // โ˜… ๋ฐ˜๋“œ์‹œ ์˜ค๋ฒ„๋ผ์ด๋“œ
    }
}

๋˜๋Š” IDE ์ž๋™ ์ƒ์„ฑ:

  • IntelliJ: Cmd + N โ†’ "equals and hashCode"
  • ๊ถŒ์žฅ โ€” ์ผ๊ด€์„ฑ ๋ณด์žฅ

Java 16+ Record โ€” equals/hashCode ์ž๋™

public record Customer(String customerId, String name) {
    // equals, hashCode, toString ์ž๋™ ์ƒ์„ฑ!
    // accessor ๋ฉ”์„œ๋“œ customerId(), name() ์ž๋™ ์ƒ์„ฑ
}

// ์‚ฌ์šฉ
Customer alice = new Customer("C001", "Alice");
Customer alice2 = new Customer("C001", "Alice");

alice.equals(alice2);   // true
alice.hashCode() == alice2.hashCode();  // true

Map<Customer, Integer> map = new HashMap<>();
map.put(alice, 100);
map.get(alice2);  // 100 โœ“

โ†’ ๋ถˆ๋ณ€ ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค์˜ ํ‘œ์ค€.


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

HashMap ์˜ ๋‚ด๋ถ€ ๊ตฌ์กฐ

public class HashMap<K, V> {
    // ํ•ต์‹ฌ ํ•„๋“œ
    transient Node<K,V>[] table;       // ๋ฐฐ์—ด (bucket)
    transient int size;                // ์š”์†Œ ์ˆ˜
    int threshold;                     // ๋‹ค์Œ rehash ์ž„๊ณ„ (= capacity * loadFactor)
    final float loadFactor;            // 0.75 ๊ธฐ๋ณธ
    
    // ์ƒ์ˆ˜
    static final int DEFAULT_INITIAL_CAPACITY = 16;     // 1 << 4
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    static final int TREEIFY_THRESHOLD = 8;             // ํŠธ๋ฆฌํ™” ์ž„๊ณ„
    static final int UNTREEIFY_THRESHOLD = 6;           // ํŠธ๋ฆฌ โ†’ ๋ฆฌ์ŠคํŠธ ์ž„๊ณ„
    static final int MIN_TREEIFY_CAPACITY = 64;         // ํŠธ๋ฆฌํ™” ์ตœ์†Œ capacity
    
    // Node ์ •์˜
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
    }
    
    // Tree Node (Java 8+)
    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;
        boolean red;
    }
}

put() ์˜ ๋™์ž‘ ๋‹จ๊ณ„ โญโญโญ

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    //                                                โ†‘
    //                              ์ƒ์œ„ 16๋น„ํŠธ โ†” ํ•˜์œ„ 16๋น„ํŠธ XOR
    //                              (ํ•ด์‹œ ๋ถ„์‚ฐ ํ–ฅ์ƒ)
}

final V putVal(int hash, K key, V value, ...) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    
    // 1. table ์ด null ์ด๋ฉด ์ดˆ๊ธฐํ™” (lazy)
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    
    // 2. bucket ์œ„์น˜ ๊ณ„์‚ฐ: hash & (n - 1)
    if ((p = tab[i = (n - 1) & hash]) == null) {
        // ๋นˆ bucket โ†’ ๊ทธ๋ƒฅ ์ถ”๊ฐ€
        tab[i] = new Node<>(hash, key, value, null);
    } else {
        // ์ถฉ๋Œ ๋ฐœ์ƒ!
        Node<K,V> e; K k;
        
        // 3. ๊ฐ™์€ ํ‚ค์ธ์ง€ ํ™•์ธ (hash + equals)
        if (p.hash == hash && 
            ((k = p.key) == key || (key != null && key.equals(k)))) {
            e = p;  // ๊ฐ™์€ ํ‚ค โ†’ ๊ฐ’๋งŒ ๊ต์ฒด
        }
        // 4. Tree Node ์ธ ๊ฒฝ์šฐ
        else if (p instanceof TreeNode) {
            e = ((TreeNode<K,V>)p).putTreeVal(...);
        }
        // 5. ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ ์ˆœํšŒ
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = new Node<>(hash, key, value, null);
                    
                    // 6. ํŠธ๋ฆฌํ™” ์ž„๊ณ„ ๋„๋‹ฌ ์‹œ ํŠธ๋ฆฌ๋กœ ์ „ํ™˜
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash && 
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        
        // 7. ๊ฐ™์€ ํ‚ค์˜€์œผ๋ฉด ๊ฐ’ ๊ต์ฒด
        if (e != null) {
            V oldValue = e.value;
            e.value = value;
            return oldValue;
        }
    }
    
    // 8. size ์ฆ๊ฐ€, ์ž„๊ณ„ ์ดˆ๊ณผ ์‹œ rehash
    ++modCount;
    if (++size > threshold)
        resize();
    return null;
}

์‹œ๊ฐํ™” โ€” put() ํ๋ฆ„

key = "Alice", value = customerObj
       โ†“
[1. hashCode ๊ณ„์‚ฐ]
"Alice".hashCode() = 63350368

[2. ํ•ด์‹œ ๋ถ„์‚ฐ]
hash = 63350368 ^ (63350368 >>> 16) = ...

[3. bucket ์ธ๋ฑ์Šค ๊ณ„์‚ฐ]
n = 16 (capacity)
i = hash & (n - 1) = hash & 15 = 5

[4. table[5] ํ™•์ธ]
table[5] == null?
โ”œโ”€โ”€ YES โ†’ ์ƒˆ Node ์ถ”๊ฐ€
โ””โ”€โ”€ NO  โ†’ ์ถฉ๋Œ!
      โ†“
   [5. ์ถฉ๋Œ ์ฒ˜๋ฆฌ]
   ๊ธฐ์กด bucket ์˜ ์ฒซ ๋…ธ๋“œ ํ™•์ธ
   โ”œโ”€โ”€ ๊ฐ™์€ ํ‚ค (equals) โ†’ ๊ฐ’๋งŒ ๊ต์ฒด
   โ”œโ”€โ”€ TreeNode โ†’ ํŠธ๋ฆฌ์— ์‚ฝ์ž…
   โ””โ”€โ”€ ์ผ๋ฐ˜ Node โ†’ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ ์ˆœํšŒ
         โ†“
      ๋งˆ์ง€๋ง‰์— ์ถ”๊ฐ€
      โ†“
   [6. binCount >= 7?]
      โ””โ”€โ”€ YES โ†’ treeifyBin()
      
[7. size++]
[8. size > threshold? โ†’ resize()]

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

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        
        // 1. ์ฒซ ๋…ธ๋“œ๊ฐ€ ์ผ์น˜ํ•˜๋Š”๊ฐ€?
        if (first.hash == hash &&
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        
        // 2. ๊ทธ ์™ธ๋Š” ๋‹ค์Œ ๋…ธ๋“œ ๋˜๋Š” ํŠธ๋ฆฌ ํƒ์ƒ‰
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

ํ•ต์‹ฌ:
1. hash ๊ณ„์‚ฐ
2. bucket ์œ„์น˜ (hash & (n-1))
3. ๊ทธ bucket ์˜ ์ฒซ ๋…ธ๋“œ โ†’ ํ‚ค ๋น„๊ต
4. ๋‹ค์Œ ๋…ธ๋“œ ๋˜๋Š” ํŠธ๋ฆฌ ํƒ์ƒ‰


capacity ๊ฐ€ 2์˜ ๊ฑฐ๋“ญ์ œ๊ณฑ์ธ ์ด์œ  โญ

// ์ผ๋ฐ˜์ ์ธ % ์—ฐ์‚ฐ
int index = hash % n;  // ๋А๋ฆผ (CPU divide)

// 2์˜ ๊ฑฐ๋“ญ์ œ๊ณฑ์ผ ๋•Œ โ€” ๋น„ํŠธ AND
int index = hash & (n - 1);  // ๋น ๋ฆ„ (CPU and)

์˜ˆ์‹œ (n = 16):

n - 1 = 15 (์ด์ง„์ˆ˜: 0000 1111)

hash = 12345678 (์ด์ง„์ˆ˜: ... 0010 0011 0001 0001 1110)
                              & 0000 0000 0000 0000 1111
                              = 0000 0000 0000 0000 1110 = 14

โ†’ index = 14

ํšจ๊ณผ:

  • % ๋ณด๋‹ค 5~10๋ฐฐ ๋น ๋ฆ„
  • HashMap ์˜ ํ•ต์‹ฌ ์ตœ์ ํ™”

ํ•ด์‹œ ๋ถ„์‚ฐ โ€” hash ^ (hash >>> 16)

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

์™œ?: hashCode ์˜ ์ƒ์œ„ ๋น„ํŠธ๋„ ํ™œ์šฉ ํ•˜๊ธฐ ์œ„ํ•ด.

๋ฌธ์ œ: hash & (n-1) ์€ ํ•˜์œ„ ๋น„ํŠธ๋งŒ ์‚ฌ์šฉ.

์˜ˆ: ๋‘ ๊ฐ์ฒด์˜ hashCode ๊ฐ€

๊ฐ์ฒด A: 0xABCD0001 (ํ•˜์œ„ 4๋น„ํŠธ: 0001)
๊ฐ์ฒด B: 0xFFFF0001 (ํ•˜์œ„ 4๋น„ํŠธ: 0001)

โ†’ n=16 ์ผ ๋•Œ ๋‘˜ ๋‹ค ์ธ๋ฑ์Šค 1! (์ถฉ๋Œ)

ํ•ด๊ฒฐ: ์ƒ์œ„ 16๋น„ํŠธ์™€ XOR

A.hash = 0xABCD0001 ^ 0x0000ABCD = 0xABCDABCC (ํ•˜์œ„ ๋น„ํŠธ๊ฐ€ ๋‹ค๋ฆ„)
B.hash = 0xFFFF0001 ^ 0x0000FFFF = 0xFFFFFFFE (ํ•˜์œ„ ๋น„ํŠธ๊ฐ€ ๋‹ค๋ฆ„)

โ†’ ๋‹ค๋ฅธ ์ธ๋ฑ์Šค์— ๋ถ„์‚ฐ!


Treeification (Java 8+) โญโญ

์–ธ์ œ ํŠธ๋ฆฌํ™”?

static final int TREEIFY_THRESHOLD = 8;       // ํ•œ bucket ์˜ ๋…ธ๋“œ๊ฐ€ 8๊ฐœ ์ด์ƒ
static final int MIN_TREEIFY_CAPACITY = 64;   // ๊ทธ๋Ÿฌ๋‚˜ capacity ๊ฐ€ 64 ๋ฏธ๋งŒ์ด๋ฉด rehash ์šฐ์„ 

์กฐ๊ฑด: bucket ์•ˆ์˜ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๊ฐ€ 8๊ฐœ ์ดˆ๊ณผ + capacity โ‰ฅ 64.


Red-Black Tree ๋กœ ์ „ํ™˜

Before (์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ):

bucket[5]: A โ†’ B โ†’ C โ†’ D โ†’ E โ†’ F โ†’ G โ†’ H โ†’ I (9๊ฐœ)

After (Red-Black Tree):

bucket[5]:
        E (black)
       / \
      C   G
     / \  / \
    A  D F  I
        / 
       H

ํšจ๊ณผ:

  • ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ ๊ฒ€์ƒ‰: O(n)
  • ํŠธ๋ฆฌ ๊ฒ€์ƒ‰: O(log n)
  • HashDoS ๊ณต๊ฒฉ ์™„ํ™”

Untreeification

static final int UNTREEIFY_THRESHOLD = 6;

resize ํ›„ ํŠธ๋ฆฌ ๋…ธ๋“œ๊ฐ€ 6๊ฐœ ์ดํ•˜ ๊ฐ€ ๋˜๋ฉด โ†’ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๋กœ ๋ณต๊ท€.

(ํŠธ๋ฆฌ ๊ด€๋ฆฌ ๋น„์šฉ์ด ๋” ํด ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ)


Rehashing (resize) โญ

์–ธ์ œ?

if (++size > threshold)
    resize();

threshold = capacity ร— loadFactor = 16 ร— 0.75 = 12

13๋ฒˆ์งธ ์ถ”๊ฐ€ ์‹œ โ†’ resize ๋ฐœ๋™.


์–ด๋–ป๊ฒŒ?

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    
    // 1. ์ƒˆ capacity = 2๋ฐฐ
    int newCap = oldCap << 1;  // oldCap * 2
    int newThr = (int)(newCap * loadFactor);
    
    // 2. ์ƒˆ ๋ฐฐ์—ด ์ƒ์„ฑ
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    
    // 3. ๊ธฐ์กด ์š”์†Œ๋ฅผ ๋ชจ๋‘ ์ƒˆ ๋ฐฐ์—ด๋กœ ์žฌ๋ฐฐ์น˜
    for (int j = 0; j < oldCap; ++j) {
        Node<K,V> e;
        if ((e = oldTab[j]) != null) {
            // ๋‹จ์ผ ๋…ธ๋“œ
            if (e.next == null) {
                newTab[e.hash & (newCap - 1)] = e;
            }
            // ํŠธ๋ฆฌ ๋…ธ๋“œ
            else if (e instanceof TreeNode) {
                ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
            }
            // ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ
            else {
                // ... ๋‘ ๊ทธ๋ฃน์œผ๋กœ ๋ถ„๋ฆฌ (lo, hi)
            }
        }
    }
    return newTab;
}

Java 8 ์˜ resize ์ตœ์ ํ™”

๊ธฐ์กด ๋ฐฉ์‹ (Java 7):

  • ๋ชจ๋“  ๋…ธ๋“œ์˜ ์ƒˆ ์ธ๋ฑ์Šค๋ฅผ ๋‹ค์‹œ ๊ณ„์‚ฐ
  • O(n) ๋น„์šฉ

Java 8 ์˜ ์˜๋ฆฌํ•œ ๋ฐฉ์‹:

  • capacity 2๋ฐฐ ํ™•์žฅ ์‹œ โ†’ ๋น„ํŠธ 1๊ฐœ ์ถ”๊ฐ€
  • ๋…ธ๋“œ๋Š” ๋‘ ๊ทธ๋ฃน ์œผ๋กœ ๊ฐˆ๋ผ์ง:
    • (hash & oldCap) == 0 โ†’ ๊ฐ™์€ ์ธ๋ฑ์Šค ์œ ์ง€
    • (hash & oldCap) != 0 โ†’ ์ธ๋ฑ์Šค + oldCap ์œผ๋กœ ์ด๋™

ํšจ๊ณผ: ์ธ๋ฑ์Šค ์žฌ๊ณ„์‚ฐ ์—†์ด ๋ถ„๋ฅ˜ โ†’ ๋น ๋ฆ„.


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

HashMap ์˜ ๋‚ด๋ถ€:

table = [
  null,
  Node(key=A, value=1) โ†’ Node(key=B, value=2),  // ์ถฉ๋Œ โ†’ ์—ฐ๊ฒฐ๋ฆฌ์ŠคํŠธ
  null,
  null,
  Node(key=C, value=3),
  TreeNode(...),  // 8๊ฐœ ์ดˆ๊ณผ โ†’ Tree
  null,
  ...
]

capacity = 16 (table.length)
size = 5 (ํ˜„์žฌ ์š”์†Œ ์ˆ˜)
threshold = 12 (capacity * 0.75)

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

์˜ˆ์‹œ 1: ILIC ์˜ ๊ณ ๊ฐ ์บ์‹œ

@Service
public class CustomerCacheService {
    
    // โœ“ ํฐ capacity ๋ฏธ๋ฆฌ ์„ค์ • โ€” Rehashing ํšŒํ”ผ
    private final Map<String, Customer> cache = new HashMap<>(10000);
    
    public void put(Customer customer) {
        cache.put(customer.getCustomerId(), customer);
    }
    
    public Customer get(String customerId) {
        return cache.get(customerId);  // O(1) ํ‰๊ท 
    }
    
    public Optional<Customer> findByName(String name) {
        // โš ๏ธ value ๊ฒ€์ƒ‰์€ O(n) โ€” ๋น„ํšจ์œจ
        return cache.values().stream()
            .filter(c -> c.getName().equals(name))
            .findFirst();
    }
    
    // ๋” ์ข‹์€ ๋ฐฉ๋ฒ•: ๋ณ„๋„ ์ธ๋ฑ์Šค
    private final Map<String, String> nameToIdIndex = new HashMap<>();
    
    public void putWithIndex(Customer customer) {
        cache.put(customer.getCustomerId(), customer);
        nameToIdIndex.put(customer.getName(), customer.getCustomerId());
    }
    
    public Optional<Customer> findByNameFast(String name) {
        String id = nameToIdIndex.get(name);  // O(1)
        return id == null ? Optional.empty() : Optional.of(cache.get(id));
    }
}

์˜ˆ์‹œ 2: equals + hashCode ํ‘œ์ค€ ๊ตฌํ˜„

public class Cargo {
    private final String cargoId;
    private final String name;
    private final int weight;
    
    // โœ“ Java 16+ ๊ถŒ์žฅ: Record ์‚ฌ์šฉ
    // public record Cargo(String cargoId, String name, int weight) {}
    
    // ๋˜๋Š” ์ผ๋ฐ˜ ํด๋ž˜์Šค + ๋ช…์‹œ์  ๊ตฌํ˜„
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Cargo)) return false;
        Cargo cargo = (Cargo) o;
        return Objects.equals(cargoId, cargo.cargoId);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(cargoId);
    }
    
    // toString ๋„ ๊ถŒ์žฅ
    @Override
    public String toString() {
        return "Cargo{cargoId='" + cargoId + "', name='" + name + "'}";
    }
}

์˜ˆ์‹œ 3: ILIC ์˜ ๋“ฑ๊ธ‰๋ณ„ ๋งค์ถœ ์ง‘๊ณ„

public class SalesAggregator {
    
    // โœ“ Java 8+ merge ํ™œ์šฉ
    public Map<String, Long> aggregateByGrade(List<Order> orders) {
        Map<String, Long> result = new HashMap<>();
        
        for (Order order : orders) {
            // grade ๋ณ„ ๋งค์ถœ ํ•ฉ๊ณ„
            result.merge(
                order.getCustomerGrade(),
                order.getAmount(),
                Long::sum
            );
        }
        
        return result;
    }
    
    // โœ“ ๋” ์šฐ์•„ํ•œ ๋ฐฉ๋ฒ•: Stream
    public Map<String, Long> aggregateByGradeStream(List<Order> orders) {
        return orders.stream()
            .collect(Collectors.groupingBy(
                Order::getCustomerGrade,
                Collectors.summingLong(Order::getAmount)
            ));
    }
    
    // โœ“ ๋‹ค์ค‘ ํ‚ค ๊ทธ๋ฃนํ•‘
    public Map<String, Map<String, Long>> aggregateByGradeAndPort(List<Order> orders) {
        return orders.stream()
            .collect(Collectors.groupingBy(
                Order::getCustomerGrade,
                Collectors.groupingBy(
                    Order::getOriginPort,
                    Collectors.summingLong(Order::getAmount)
                )
            ));
    }
}

์˜ˆ์‹œ 4: ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ์บ์‹œ โ€” ConcurrentHashMap

@Service
public class FareCache {
    
    // โœ“ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ์•ˆ์ „
    private final ConcurrentHashMap<String, BigDecimal> cache = new ConcurrentHashMap<>();
    
    public BigDecimal getFare(String routeKey) {
        // computeIfAbsent โ€” atomic ์—ฐ์‚ฐ
        return cache.computeIfAbsent(routeKey, this::calculateFare);
    }
    
    private BigDecimal calculateFare(String routeKey) {
        // ๋ฌด๊ฑฐ์šด ๊ณ„์‚ฐ (DB ์กฐํšŒ, API ํ˜ธ์ถœ ๋“ฑ)
        return fareCalculator.calculate(routeKey);
    }
    
    public void invalidate(String routeKey) {
        cache.remove(routeKey);
    }
    
    public void invalidateAll() {
        cache.clear();
    }
}

computeIfAbsent ์˜ ๋งˆ๋ฒ•:

  • ํ‚ค๊ฐ€ ์—†์œผ๋ฉด โ†’ ๋žŒ๋‹ค ์‹คํ–‰ โ†’ ๊ฒฐ๊ณผ ์ €์žฅ
  • ํ‚ค๊ฐ€ ์žˆ์œผ๋ฉด โ†’ ๊ฐ’ ๋ฐ˜ํ™˜ (๋žŒ๋‹ค ์‹คํ–‰ X)
  • ์›์ž์„ฑ ๋ณด์žฅ (์Šค๋ ˆ๋“œ ์•ˆ์ „)

์˜ˆ์‹œ 5: HashMap ์˜ ํ•จ์ • ์‹œ์—ฐ

public class HashMapPitfalls {
    
    // โŒ ํ•จ์ • 1: equals ๋งŒ ์˜ค๋ฒ„๋ผ์ด๋“œ
    public static void pitfall1() {
        Map<BadKey, String> map = new HashMap<>();
        BadKey k1 = new BadKey("A");
        BadKey k2 = new BadKey("A");
        
        map.put(k1, "value");
        System.out.println(map.get(k2));  // null!
        // (BadKey ๊ฐ€ hashCode ์˜ค๋ฒ„๋ผ์ด๋“œ X)
    }
    
    // โŒ ํ•จ์ • 2: ๊ฐ€๋ณ€ ํ‚ค
    public static void pitfall2() {
        Map<List<Integer>, String> map = new HashMap<>();
        List<Integer> key = new ArrayList<>(List.of(1, 2));
        
        map.put(key, "value");
        key.add(3);  // hashCode ๋ณ€๊ฒฝ!
        
        System.out.println(map.get(key));  // null!
        System.out.println(map.containsKey(key));  // false!
    }
    
    // โŒ ํ•จ์ • 3: ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ + HashMap
    public static void pitfall3() {
        Map<Integer, Integer> map = new HashMap<>();
        
        // ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ put
        IntStream.range(0, 100).parallel().forEach(i -> {
            map.put(i, i * 2);  // ๋ฐ์ดํ„ฐ ์†์ƒ ๊ฐ€๋Šฅ
        });
        // ๊ฒฐ๊ณผ: size ๊ฐ€ 100 ๋ฏธ๋งŒ์ด๊ฑฐ๋‚˜, ๋ฌดํ•œ ๋ฃจํ”„ ๋ฐœ์ƒ ๊ฐ€๋Šฅ
    }
    
    // โœ“ ์•ˆ์ „
    public static void safe() {
        Map<Integer, Integer> map = new ConcurrentHashMap<>();
        IntStream.range(0, 100).parallel().forEach(i -> {
            map.put(i, i * 2);  // ์•ˆ์ „
        });
    }
}

์˜ˆ์‹œ 6: ์–‘๋ฐฉํ–ฅ ๋งคํ•‘

public class BidirectionalMap {
    
    // ILIC: ๊ณ ๊ฐ ID โ†” ์ด๋ฉ”์ผ ๋งคํ•‘
    private final Map<String, String> idToEmail = new HashMap<>();
    private final Map<String, String> emailToId = new HashMap<>();
    
    public void put(String customerId, String email) {
        // ๊ธฐ์กด ๋งคํ•‘ ์ œ๊ฑฐ
        String oldEmail = idToEmail.put(customerId, email);
        if (oldEmail != null) {
            emailToId.remove(oldEmail);
        }
        
        String oldId = emailToId.put(email, customerId);
        if (oldId != null) {
            idToEmail.remove(oldId);
        }
    }
    
    public String getEmail(String customerId) {
        return idToEmail.get(customerId);
    }
    
    public String getId(String email) {
        return emailToId.get(email);
    }
}

์˜ˆ์‹œ 7: HashMap ์ˆœํšŒ ํŒจํ„ด

public class HashMapIteration {
    
    public void iterate(Map<String, Customer> map) {
        // 1. entrySet โ€” ๊ฐ€์žฅ ํšจ์œจ
        for (Map.Entry<String, Customer> entry : map.entrySet()) {
            String key = entry.getKey();
            Customer value = entry.getValue();
            // ...
        }
        
        // 2. keySet โ€” ํ‚ค๋งŒ ํ•„์š”ํ•  ๋•Œ
        for (String key : map.keySet()) {
            // ...
        }
        
        // 3. values โ€” ๊ฐ’๋งŒ ํ•„์š”ํ•  ๋•Œ
        for (Customer customer : map.values()) {
            // ...
        }
        
        // 4. forEach (Java 8+)
        map.forEach((key, value) -> {
            System.out.println(key + " = " + value.getName());
        });
        
        // 5. Stream
        map.entrySet().stream()
            .filter(e -> e.getValue().isVip())
            .forEach(e -> System.out.println(e.getKey()));
    }
    
    // โŒ ํ•จ์ •: ์ˆœํšŒ ์ค‘ ์ˆ˜์ •
    public void unsafeRemoval(Map<String, Customer> map) {
        for (String key : map.keySet()) {
            if (map.get(key).isExpired()) {
                map.remove(key);  // ConcurrentModificationException!
            }
        }
    }
    
    // โœ“ Iterator
    public void safeRemoval1(Map<String, Customer> map) {
        Iterator<Map.Entry<String, Customer>> iter = map.entrySet().iterator();
        while (iter.hasNext()) {
            if (iter.next().getValue().isExpired()) {
                iter.remove();
            }
        }
    }
    
    // โœ“ Java 8+ removeIf
    public void safeRemoval2(Map<String, Customer> map) {
        map.entrySet().removeIf(e -> e.getValue().isExpired());
    }
}

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

์‹ค์ˆ˜ 1: equals ๋งŒ ์˜ค๋ฒ„๋ผ์ด๋“œ, hashCode ์•ˆ ํ•จ

// โŒ ์œ„ํ—˜
public class Customer {
    @Override
    public boolean equals(Object o) { /* customerId ๋น„๊ต */ }
    // hashCode() ์˜ค๋ฒ„๋ผ์ด๋“œ X
}

ํ•ด๊ฒฐ: ๋‘˜ ๋‹ค ์˜ค๋ฒ„๋ผ์ด๋“œ (๋˜๋Š” Record ์‚ฌ์šฉ).


์‹ค์ˆ˜ 2: ๊ฐ€๋ณ€ ๊ฐ์ฒด๋ฅผ ํ‚ค๋กœ ์‚ฌ์šฉ

// โŒ ์œ„ํ—˜
Map<List<String>, Integer> map = new HashMap<>();
List<String> key = new ArrayList<>();
map.put(key, 1);
key.add("X");  // hashCode ๋ณ€๊ฒฝ โ†’ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜

ํ•ด๊ฒฐ: ๋ถˆ๋ณ€ ๊ฐ์ฒด (String, Integer, ๋ถˆ๋ณ€ ํด๋ž˜์Šค).


์‹ค์ˆ˜ 3: ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ + HashMap

// โŒ ๋ฐ์ดํ„ฐ ์†์ƒ + ๋ฌดํ•œ ๋ฃจํ”„ ๊ฐ€๋Šฅ
Map<String, String> map = new HashMap<>();

ํ•ด๊ฒฐ: ConcurrentHashMap.


์‹ค์ˆ˜ 4: containsValue ๋‚จ์šฉ

// โŒ O(n)
if (map.containsValue(target)) { ... }

ํ•ด๊ฒฐ: value โ†’ key ์ธ๋ฑ์Šค ๋ณ„๋„ ์œ ์ง€ (์–‘๋ฐฉํ–ฅ ๋งคํ•‘).


์‹ค์ˆ˜ 5: Iterator ์—†์ด ์ˆœํšŒ ์ค‘ ์ˆ˜์ •

// โŒ ConcurrentModificationException
for (String key : map.keySet()) {
    map.remove(key);
}

ํ•ด๊ฒฐ: Iterator.remove() ๋˜๋Š” removeIf.


์‹ค์ˆ˜ 6: capacity ๋ฏธ์„ค์ •

// โŒ 100๋งŒ ๋ฐ์ดํ„ฐ์— ๊ธฐ๋ณธ capacity
Map<String, String> map = new HashMap<>();
for (int i = 0; i < 1_000_000; i++) {
    map.put(...);  // ์•ฝ 17๋ฒˆ rehashing!
}

ํ•ด๊ฒฐ:

Map<String, String> map = new HashMap<>(2_097_152);  // 2์˜ ๊ฑฐ๋“ญ์ œ๊ณฑ

์‹ค์ˆ˜ 7: null ํ‚ค/๊ฐ’ ํ˜ผ๋™

HashMap<String, String> hm = new HashMap<>();
hm.put(null, "value");  // โœ“ ๊ฐ€๋Šฅ

Hashtable<String, String> ht = new Hashtable<>();
ht.put(null, "value");  // NullPointerException

ConcurrentHashMap<String, String> chm = new ConcurrentHashMap<>();
chm.put(null, "value");  // NullPointerException

์›์น™: ConcurrentHashMap ์€ null ํ‚ค/๊ฐ’ ํ—ˆ์šฉ X.


์‹ค์ˆ˜ 8: ์ˆœ์„œ ๊ธฐ๋Œ€

HashMap<Integer, String> map = new HashMap<>();
map.put(1, "A");
map.put(2, "B");
map.put(3, "C");

for (Map.Entry<Integer, String> e : map.entrySet()) {
    System.out.println(e.getKey());  // ์ˆœ์„œ ๋ณด์žฅ X!
}

ํ•ด๊ฒฐ:

  • ์‚ฝ์ž… ์ˆœ์„œ โ†’ LinkedHashMap (Unit 6.5)
  • ์ •๋ ฌ ์ˆœ์„œ โ†’ TreeMap (Unit 6.5)

๐Ÿ”— 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 ๋‚ด๋ถ€ ๊ตฌ์กฐ] โ˜…โ˜…โ˜… โ† ์ง€๊ธˆ ์—ฌ๊ธฐ (Phase 6 ์ •์ )
        โ†“
[Unit 6.5: TreeMap, LinkedHashMap]

Phase 4-5 ์™€์˜ ์—ฐ๊ฒฐ

Phase์—ฐ๊ฒฐ
4.1 (JVM ๋ฉ”๋ชจ๋ฆฌ)HashMap ์˜ ๋ฐฐ์—ด์€ Heap ์—
5.1 (GC)ํฐ HashMap ์˜ GC ๋ถ€๋‹ด
6.1 (String)String ์˜ hashCode ์บ์‹ฑ
6.3 (ArrayList)bucket ์•ˆ์˜ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ

์ž๋ฐ” ์ปฌ๋ ‰์…˜์˜ ์œ„์น˜

Map (์ธํ„ฐํŽ˜์ด์Šค)
โ”œโ”€โ”€ HashMap โญ (์ด Unit)
โ”œโ”€โ”€ LinkedHashMap (Unit 6.5)
โ”œโ”€โ”€ TreeMap (Unit 6.5)
โ”œโ”€โ”€ ConcurrentHashMap (๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ)
โ””โ”€โ”€ Hashtable (legacy)

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

์งˆ๋ฌธ์ด Unit ์—์„œ์˜ ๋‹ต
HashMap ๋‚ด๋ถ€ ๊ตฌ์กฐ?๋ฐฐ์—ด + ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ + ํŠธ๋ฆฌ
equals + hashCode ๊ด€๊ณ„?๊ณ„์•ฝ (equals ๊ฐ™์œผ๋ฉด hashCode ๊ฐ™์•„์•ผ)
ํ•ด์‹œ ์ถฉ๋Œ ์ฒ˜๋ฆฌ?Chaining + Treeification (Java 8+)
Load Factor ์˜๋ฏธ?0.75 โ€” ์‹œ๊ฐ„/๊ณต๊ฐ„ trade-off
Treeification ์ž„๊ณ„?TREEIFY_THRESHOLD = 8
capacity ๊ฐ€ 2^n ์ธ ์ด์œ ?hash & (n-1) ๋น„ํŠธ ์—ฐ์‚ฐ
HashMap vs ConcurrentHashMap?๋™์‹œ์„ฑ ์ฐจ์ด

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

์–ธ์–ดMap ๊ตฌํ˜„
JavaHashMap
Pythondict
JavaScriptMap, Object
C++std::unordered_map
Gomap[K]V
RustHashMap<K, V>

โ†’ ๊ฑฐ์˜ ๋ชจ๋“  ์–ธ์–ด์— ํ•ด์‹œ ํ…Œ์ด๋ธ” ์กด์žฌ.


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

1๏ธโƒฃ HashMap = ๋ฐฐ์—ด + ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ + ํŠธ๋ฆฌ (Java 8+).

๋ฐฐ์—ด (bucket) ์œผ๋กœ O(1) ์ ‘๊ทผ. ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ ๋กœ ์ถฉ๋Œ ์ฒ˜๋ฆฌ. Red-Black Tree ๋กœ ์ถฉ๋Œ ํญ์ฆ ์‹œ O(log n) ๋ณด์žฅ (TREEIFY_THRESHOLD=8). capacity ๋Š” 2์˜ ๊ฑฐ๋“ญ์ œ๊ณฑ โ€” hash & (n-1) ๋น„ํŠธ ์—ฐ์‚ฐ์œผ๋กœ ๋น ๋ฅธ ์ธ๋ฑ์Šค ๊ณ„์‚ฐ. Load Factor 0.75 ๋„๋‹ฌ ์‹œ Rehashing (2๋ฐฐ ํ™•์žฅ).

2๏ธโƒฃ hashCode + equals ๊ณ„์•ฝ โ€” ๊ฐ™์œผ๋ฉด hashCode ๋„ ๊ฐ™์•„์•ผ.

a.equals(b) โ†’ a.hashCode() == b.hashCode() ๋ฐ˜๋“œ์‹œ ๋ณด์žฅ. ์—ญ์€ ์„ฑ๋ฆฝ X (์ถฉ๋Œ ๊ฐ€๋Šฅ). equals ์˜ค๋ฒ„๋ผ์ด๋“œ ์‹œ hashCode ๋„ ๋ฐ˜๋“œ์‹œ ์˜ค๋ฒ„๋ผ์ด๋“œ. ์œ„๋ฐ˜ ์‹œ โ†’ HashMap ๋ง๊ฐ€์ง (์บ์‹œ ์•ˆ ๋จนํž˜, ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜). Record (Java 16+) ๋˜๋Š” Objects.hash() + IDE ์ž๋™ ์ƒ์„ฑ ๊ถŒ์žฅ. ๋ถˆ๋ณ€ ํ‚ค ์‚ฌ์šฉ (๊ฐ€๋ณ€ ํ‚ค๋Š” ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์œ„ํ—˜).

3๏ธโƒฃ ์‹ค๋ฌด ์›์น™ โ€” capacity ๋ฏธ๋ฆฌ ์„ค์ •, ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ์‹œ ConcurrentHashMap, Java 8+ ๋ฉ”์„œ๋“œ ํ™œ์šฉ.

ํฐ ๋ฐ์ดํ„ฐ๋Š” new HashMap<>(์˜ˆ์ƒํฌ๊ธฐ / 0.75) ๋กœ ๋ฏธ๋ฆฌ ํ• ๋‹น โ†’ Rehashing ํšŒํ”ผ. ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ = ConcurrentHashMap (HashMap ์€ ๋ฌดํ•œ ๋ฃจํ”„ ์œ„ํ—˜). merge(), computeIfAbsent() ๋“ฑ Java 8+ ๋ฉ”์„œ๋“œ๋กœ 1๋ฒˆ์˜ ํ•ด์‹œ ์—ฐ์‚ฐ. ์ˆœ์„œ ํ•„์š” = LinkedHashMap (Unit 6.5). ์ •๋ ฌ ํ•„์š” = TreeMap. ILIC ์˜ ๋ชจ๋“  ์บ์‹œ/์ธ๋ฑ์Šค/๊ทธ๋ฃนํ•‘์˜ ํ•ต์‹ฌ ๋„๊ตฌ.


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

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

  • HashMap ์˜ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ๊ทธ๋ฆผ์œผ๋กœ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋‹ค (๋ฐฐ์—ด + ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ)
  • hashCode + equals ์˜ ๊ณ„์•ฝ์„ ์ •ํ™•ํžˆ ์•ˆ๋‹ค
  • capacity ๊ฐ€ 2์˜ ๊ฑฐ๋“ญ์ œ๊ณฑ์ธ ์ด์œ ๋ฅผ ์•ˆ๋‹ค (hash & (n-1))
  • Load Factor ์™€ Rehashing ์˜ ๊ด€๊ณ„๋ฅผ ์•ˆ๋‹ค

์‹ค์ „ ์ ์šฉ

  • equals + hashCode ๋ฅผ ํ•ญ์ƒ ํ•จ๊ป˜ ์˜ค๋ฒ„๋ผ์ด๋“œํ•œ๋‹ค
  • ํฐ ๋ฐ์ดํ„ฐ ์‹œ capacity ๋ฅผ ๋ฏธ๋ฆฌ ์„ค์ •ํ•œ๋‹ค
  • ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ConcurrentHashMap ์„ ์„ ํƒํ•œ๋‹ค
  • Java 8+ ๋ฉ”์„œ๋“œ (merge, computeIfAbsent) ๋ฅผ ํ™œ์šฉํ•œ๋‹ค

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

  • "HashMap ๋‚ด๋ถ€ ๊ตฌ์กฐ?" ๋‹ต๋ณ€ ๊ฐ€๋Šฅ (๋ฐฐ์—ด + ๋ฆฌ์ŠคํŠธ + ํŠธ๋ฆฌ)
  • "Java 8+ Treeification?" ๋‹ต๋ณ€ ๊ฐ€๋Šฅ (TREEIFY_THRESHOLD=8)
  • "Load Factor 0.75 ์˜๋ฏธ?" ๋‹ต๋ณ€ ๊ฐ€๋Šฅ
  • "ConcurrentHashMap ์ฐจ์ด?" ๋‹ต๋ณ€ ๊ฐ€๋Šฅ

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

Q1: hashCode ๋งŒ ๊ฐ™๊ณ  equals ๊ฐ€ false ์ธ ๋‘ ๊ฐ์ฒด๋ฅผ HashMap ์— ๋„ฃ์œผ๋ฉด ์–ด๋–ป๊ฒŒ ๋˜๋Š”๊ฐ€?

ํ•œ ์ค„ ๋‹ต: ๊ฐ™์€ bucket ์˜ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ ์— ๋‘˜ ๋‹ค ์ €์žฅ๋จ. ๊ฒ€์ƒ‰ ์‹œ hashCode ๋กœ bucket ์ฐพ๊ณ , equals ๋กœ ์ •ํ™•ํ•œ ๋…ธ๋“œ ์‹๋ณ„.

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

์‹œ๋‚˜๋ฆฌ์˜ค ์„ค์ •

public class Customer {
    private String customerId;
    
    @Override
    public int hashCode() {
        return 5;  // ๋ชจ๋‘ 5 ๋ฐ˜ํ™˜ (๊ทน๋‹จ์  ์˜ˆ)
    }
    
    @Override
    public boolean equals(Object o) {
        return customerId.equals(((Customer) o).customerId);
    }
}

Map<Customer, Integer> map = new HashMap<>();
map.put(new Customer("C001"), 100);
map.put(new Customer("C002"), 200);
map.put(new Customer("C003"), 300);

put ์‹œ ๋™์ž‘

iteration 1: put(C001, 100)

1. hashCode = 5
2. bucket index = 5 & (16-1) = 5
3. table[5] == null โ†’ ์ƒˆ Node ์ถ”๊ฐ€

table[5]: Node(C001, 100)

iteration 2: put(C002, 200)

1. hashCode = 5
2. bucket index = 5
3. table[5] != null โ†’ ์ถฉ๋Œ!
4. ์ฒซ ๋…ธ๋“œ (C001) ์™€ equals ๋น„๊ต: false
5. next == null โ†’ ์ƒˆ Node ๋ฅผ ๋์— ์ถ”๊ฐ€

table[5]: Node(C001, 100) โ†’ Node(C002, 200)

iteration 3: put(C003, 300)

table[5]: Node(C001, 100) โ†’ Node(C002, 200) โ†’ Node(C003, 300)

get ์‹œ ๋™์ž‘

Customer key = new Customer("C002");
map.get(key);
1. hashCode = 5
2. bucket index = 5
3. table[5] ์˜ ์ฒซ ๋…ธ๋“œ ํ™•์ธ
4. ์ฒซ ๋…ธ๋“œ C001 ๊ณผ equals ๋น„๊ต: false
5. next ๋กœ ์ด๋™ โ†’ C002 ์™€ equals ๋น„๊ต: true!
6. ๊ทธ ๋…ธ๋“œ์˜ value ๋ฐ˜ํ™˜: 200

โ†’ equals ๋กœ ์ •ํ™•ํ•œ ๋…ธ๋“œ ์‹๋ณ„.


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

  • ์ถฉ๋Œ ์—†์œผ๋ฉด: O(1)
  • ์ถฉ๋Œ 1๊ฐœ: O(2) โ‰ˆ O(1)
  • ์ถฉ๋Œ 8๊ฐœ: O(8) โ‰ˆ O(1)
  • ์ถฉ๋Œ 100๊ฐœ: O(100) โ†’ ํŠธ๋ฆฌํ™” ์‹œ O(log 100)

Java 8+ ์˜ ๊ตฌ์›

8๊ฐœ ์ดˆ๊ณผ ์‹œ โ†’ Red-Black Tree ๋กœ ์ „ํ™˜.

Before (8๊ฐœ ์ดํ•˜):
table[5]: A โ†’ B โ†’ C โ†’ D โ†’ E โ†’ F โ†’ G โ†’ H

After (9๊ฐœ์งธ ์ถ”๊ฐ€ ์‹œ):
table[5] (Tree):
        E
       / \
      C   G
     / \  / \
    A  D F  I
        / 
       H

โ†’ ๊ฒ€์ƒ‰ O(n) โ†’ O(log n).


๊ฒฐ๋ก 

"hashCode ๊ฐ€ ๊ฐ™์œผ๋ฉด ๊ฐ™์€ bucket ์— ์ €์žฅ. equals ๋กœ ์ •ํ™•ํ•œ ๋…ธ๋“œ ์‹๋ณ„.
์ผ๋ฐ˜์ ์œผ๋กœ ์ถฉ๋Œ์€ ๊ฑฐ์˜ ์—†์œผ๋ฏ€๋กœ O(1).
์ถฉ๋Œ์ด ๋งŽ์•„๋„ Java 8+ ์˜ ํŠธ๋ฆฌํ™”๋กœ ์ตœ์•… O(log n) ๋ณด์žฅ.
๋‹จ, equals ๋ฅผ ์ž˜๋ชป ๊ตฌํ˜„ํ•˜๋ฉด HashMap ์ž์ฒด๊ฐ€ ๋ง๊ฐ€์ง."


Q2: Java 8+ ์˜ Treeification ์€ ์™œ ํ•„์š”ํ•œ๊ฐ€?

ํ•œ ์ค„ ๋‹ต: HashDoS ๊ณต๊ฒฉ ์™„ํ™” + ์ตœ์•…์˜ ์„ฑ๋Šฅ ๋ณด์žฅ. ์ถฉ๋Œ ํญ์ฆ ์‹œ O(n) โ†’ O(log n).

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

1. Java 7 ์˜ ๋ฌธ์ œ โ€” HashDoS ๊ณต๊ฒฉ

๊ณต๊ฒฉ ์‹œ๋‚˜๋ฆฌ์˜ค:

// ์•…์˜์  ์‚ฌ์šฉ์ž๊ฐ€ ๋ชจ๋‘ ๊ฐ™์€ hashCode ๋ฅผ ๊ฐ€์ง„ ํ‚ค 1๋งŒ ๊ฐœ ์ „์†ก
for (int i = 0; i < 10000; i++) {
    map.put(maliciousKey(i), value);  // ๋ชจ๋‘ hashCode = 0
}

// HashMap ์˜ ๊ฒฐ๊ณผ:
// table[0]: Node1 โ†’ Node2 โ†’ Node3 โ†’ ... โ†’ Node10000 (์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ)

๊ฒ€์ƒ‰ ๋น„์šฉ:

  • O(10000) per ์กฐํšŒ
  • 1๋งŒ ๋ฒˆ ์กฐํšŒ โ†’ 1์–ต ๋ฒˆ ๋น„๊ต
  • โ†’ CPU 100% + ์„œ๋น„์Šค ๋งˆ๋น„

์‹ค์ œ ์‚ฌ๊ฑด:

  • 2011๋…„ Java 7 ์˜ ํ•ด์‹œ ์ถฉ๋Œ ๊ณต๊ฒฉ ๋ฐœ๊ฒฌ
  • ์›น ์„œ๋ฒ„ ๋งˆ๋น„ ์‚ฌ๋ก€ ๋‹ค์ˆ˜

2. Java 8 ์˜ ๋‹ต โ€” Red-Black Tree

์กฐ๊ฑด:

  • bucket ์˜ ๋…ธ๋“œ ์ˆ˜ > 8 (TREEIFY_THRESHOLD)
  • table ์˜ capacity โ‰ฅ 64 (MIN_TREEIFY_CAPACITY)
    • capacity ์ž‘์œผ๋ฉด rehash ๊ฐ€ ๋” ํšจ๊ณผ์ 

์ „ํ™˜:

9๋ฒˆ์งธ ๋…ธ๋“œ ์ถ”๊ฐ€ ์‹œ:
์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ โ†’ Red-Black Tree

ํšจ๊ณผ:

๊ฒ€์ƒ‰ O(n) โ†’ O(log n)
1๋งŒ ๊ฐœ ๋…ธ๋“œ:
- ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ: ํ‰๊ท  5์ฒœ ๋ฒˆ ๋น„๊ต
- Red-Black Tree: ์•ฝ 13๋ฒˆ ๋น„๊ต (logโ‚‚ 10000 โ‰ˆ 13)

3. ์™œ 8๊ฐœ ์ด์ƒ๋ถ€ํ„ฐ?

์ด๋ก ์  ๋ถ„์„:

  • hashCode ๊ฐ€ ์ž˜ ๋ถ„ํฌ๋˜๋ฉด (์ข‹์€ hash function)
  • 8๊ฐœ ์ด์ƒ ์ถฉ๋Œ ํ™•๋ฅ  = ํฌ์•„์†ก ๋ถ„ํฌ ๊ธฐ์ค€ 0.00000006
  • ๊ฑฐ์˜ ์ผ์–ด๋‚˜์ง€ ์•Š์Œ

์ฆ‰, 8๊ฐœ ์ด์ƒ = "๋น„์ •์ƒ" ์ƒํ™ฉ:

  • ์˜๋„์  ๊ณต๊ฒฉ
  • ๋งค์šฐ ๋‚˜์œ hashCode ๊ตฌํ˜„

โ†’ ๊ทธ๋•Œ๋งŒ ํŠธ๋ฆฌํ™” (์˜ค๋ฒ„ํ—ค๋“œ ํšŒํ”ผ).


4. ์™œ Red-Black Tree?

์„ ํƒ์ง€:

  • AVL Tree: ๋” ๊ท ํ˜•, ๊ทธ๋Ÿฌ๋‚˜ ํšŒ์ „ ๋” ๋งŽ์Œ
  • Red-Black Tree: ์•ฝ๊ฐ„ ๋œ ๊ท ํ˜•, ํšŒ์ „ ์ ์Œ (์‚ฝ์ž…/์‚ญ์ œ ๋น ๋ฆ„)
  • B-Tree: ๋””์Šคํฌ ์นœํ™” (๋ฉ”๋ชจ๋ฆฌ์—๋Š” ๋ถˆํ•„์š”)

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

  • ์‚ฝ์ž… O(log n)
  • ๊ฒ€์ƒ‰ O(log n)
  • ์‚ญ์ œ O(log n)
  • ๊ท ํ˜• ๋ณด์žฅ: ์ตœ์•… ๊นŠ์ด โ‰ค 2 ร— log n

5. Untreeification

resize ํ›„ bucket ์˜ ๋…ธ๋“œ ์ˆ˜๊ฐ€ 6 ์ดํ•˜ (UNTREEIFY_THRESHOLD) ๊ฐ€ ๋˜๋ฉด:

Tree โ†’ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ

์™œ?:

  • ํŠธ๋ฆฌ๋Š” ๋ฉ”๋ชจ๋ฆฌ ์˜ค๋ฒ„ํ—ค๋“œ ํผ (TreeNode ๊ฐ€ Node ๋ณด๋‹ค ํผ)
  • ์ ์€ ๋…ธ๋“œ๋Š” ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋” ํšจ์œจ
  • Hysteresis (์ด๋ ฅ ํšจ๊ณผ) โ€” 6๊ณผ 8์˜ ๊ฐ„๊ฒฉ์œผ๋กœ ์žฆ์€ ๋ณ€ํ™˜ ํšŒํ”ผ

6. ํŠธ๋ฆฌํ™”์˜ ํ•œ๊ณ„

์ถฉ๋Œ์ด ๋งŽ์•„๋„ capacity ๊ฐ€ ์ž‘์œผ๋ฉด:

if (tab.length < MIN_TREEIFY_CAPACITY) {
    resize();  // ํŠธ๋ฆฌ ๋Œ€์‹  rehash
}

์ด์œ : ์ž‘์€ table ์—์„œ ์ถฉ๋Œ = capacity ๊ฐ€ ๋ถ€์กฑํ•  ๊ฐ€๋Šฅ์„ฑ โ†’ rehash ๊ฐ€ ๋” ํšจ๊ณผ์ .


7. ์‹ค์ธก โ€” Java 7 vs Java 8

// ๋ชจ๋“  ํ‚ค๊ฐ€ ๊ฐ™์€ hashCode (HashDoS ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
Map<BadKey, Integer> map = new HashMap<>();
for (int i = 0; i < 100000; i++) {
    map.put(new BadKey(i), i);
}

// 1๋งŒ ๋ฒˆ ์กฐํšŒ
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
    map.get(new BadKey(i % 100000));
}
long elapsed = System.currentTimeMillis() - start;

// Java 7: ~ 60์ดˆ (์‚ฌ์‹ค์ƒ ๋ฉˆ์ถค)
// Java 8: ~ 100ms (ํŠธ๋ฆฌํ™”)

์ฐจ์ด: 600๋ฐฐ ์„ฑ๋Šฅ ์ฐจ์ด.


8. ๊ฒฐ๋ก 

"Treeification ์€ HashMap ์˜ ์•ˆ์ „๋ง.
์ •์ƒ ์ƒํ™ฉ์—์„œ๋Š” ๊ฑฐ์˜ ํŠธ๋ฆฌํ™”๋˜์ง€ ์•Š์Œ (8๊ฐœ ์ด์ƒ ์ถฉ๋Œ ํ™•๋ฅ  1์–ต๋ถ„์˜ 1).
๊ทธ๋Ÿฌ๋‚˜ HashDoS ๊ณต๊ฒฉ์ด๋‚˜ ์ž˜๋ชป๋œ hashCode ๊ตฌํ˜„ ์— ๋Œ€๋น„ํ•ด ์ตœ์•… O(log n) ๋ณด์žฅ.
Java 8+ ์˜ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ปฌ๋ ‰์…˜ ๊ฐœ์„  ์ค‘ ํ•˜๋‚˜.
์‹œ๋‹ˆ์–ด ์ฐจ๋ณ„ํ™” ํ‚ค์›Œ๋“œ: TREEIFY_THRESHOLD=8, MIN_TREEIFY_CAPACITY=64, Red-Black Tree, HashDoS."


๋‹ค์Œ Unit์œผ๋กœ

  • TreeMap, LinkedHashMap ํ•™์Šต ์ค€๋น„ ์™„๋ฃŒ
  • ์ •๋ ฌ๋œ Map ์˜ ์‚ฌ์šฉ์ฒ˜๊ฐ€ ๊ถ๊ธˆํ•˜๋‹ค
  • ์‚ฝ์ž… ์ˆœ์„œ ์œ ์ง€์˜ ํ™œ์šฉ์„ ๋งŒ๋‚  ์ค€๋น„ ์™„๋ฃŒ
profile
Software Developer

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