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 ๋งคํ, ๋ชจ๋ ๊ทธ๋ฃนํ์ ๊ธฐ๋ฐ.
๋ํ ์ฐ์ฒด๊ตญ์ ์ฌ๋ฌผํจ ์์คํ ์ ์์ํด๋ณด์ธ์.
ํธ์ง ๋๋ฏธ:
[Alice ํธ์ง][Bob ํธ์ง][Charlie ํธ์ง][David ํธ์ง]...
์ฌ๋ฌผํจ:
[101] [102] [103] [104] [105] ... [999]
โ โ โ โ
Alice - Bob Charlie
ํธ์ง ํธ์ง ํธ์ง
๊ท์น: "์ด๋ฆ โ ์ฌ๋ฌผํจ ๋ฒํธ" ๋ณํ ํจ์ (ํด์ ํจ์)
์ฐพ๊ธฐ:
"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 ๋ก ์ ํํ ๋น๊ต)
โ ๋น ๋ฅด๊ฒ ์ฑ ์ฐพ๊ธฐ.
๋ฐ์ดํฐ ๊ฒ์์ ์ต์ :
IBM ์ Hans Peter Luhn ์ด ์ ์:
ํต์ฌ ์์ด๋์ด:
key โ hash(key) โ array[hash(key)] = value
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+): ์ถฉ๋ ๋๋ฌด ๋ง์ ๋
public class Object {
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
}
์ฉ๋: ๊ฐ์ฒด๋ฅผ ์ ์ ์ธ๋ฑ์ค ๋ก ๋ณํ.
"Alice".hashCode() // 63350368
"Bob".hashCode() // 66486
๊ฐ์ hashCode ์ธ ๋ ๊ฐ์ฒด๊ฐ ์ง์ง ๊ฐ์๊ฐ? ๊ฒ์ฆ.
String a = "Alice";
String b = "Alice";
a.hashCode() == b.hashCode(); // true (๋น์ฐ)
a.equals(b); // true (๊ฐ ๋น๊ต)
๊ท์น 1: a.equals(b) ๊ฐ true ๋ผ๋ฉด, a.hashCode() == b.hashCode() ๋ ๋ฐ๋์ true.
๊ท์น 2: a.hashCode() == b.hashCode() ๋ผ๊ณ ํด์ a.equals(b) ๊ฐ true ์ธ ๊ฑด ์๋ (์ถฉ๋ ๊ฐ๋ฅ).
์ฆ:
// โ 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 ์ ํค๊ฐ ๋ ์ ์์ด์ผ ํ๋ค๋ ์๋ฐ์ ์ฒ ํ.
// โ ์ด์ ์ฌ๊ณ ์ฝ๋
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 ๋ฐํ!
}
}
๋ฌธ์ :
์ฆ์:
ํด๊ฒฐ:
@Override
public int hashCode() {
return Objects.hash(customerId);
}
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))
// โ ์ํ
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 ๋ณ๊ฒฝ๋จ)
๋ฌธ์ :
ํด๊ฒฐ: ๋ถ๋ณ ๊ฐ์ฒด๋ฅผ ํค๋ก ์ฌ์ฉ (String, Integer, ๋ถ๋ณ ํด๋์ค).
// ๋ชจ๋ ํค๊ฐ ๊ฐ์ 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):
Java 8+ ์ ๋ต:
@Service
public class GlobalCacheService {
private Map<String, String> cache = new HashMap<>(); // โ ์ํ
public void put(String k, String v) {
cache.put(k, v); // ์ฌ๋ฌ ์ค๋ ๋ ๋์ ํธ์ถ โ ๋ฐ์ดํฐ ์์
}
}
๋ฌธ์ (ํนํ Java 7 ์ด์ ):
ํด๊ฒฐ:
private Map<String, String> cache = new ConcurrentHashMap<>(); // โ
// โ ๋นํจ์จ
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 | ์บ์ ์ ๋จนํ | ์ ํํ ๋์ |
| ๋ฉด์ ๋ต๋ณ | ํ๋ฉด์ | ์๋์ด |
| ๊ฐ๋ณ ํค | ๋ฉ๋ชจ๋ฆฌ ๋์ | ๋ถ๋ณ ํค ์ฌ์ฉ |
| ํด์ ์ถฉ๋ ๊ณต๊ฒฉ | HashDoS | Tree ๋ก ์ํ |
| ๋ฉํฐ์ค๋ ๋ | ๋ฌดํ ๋ฃจํ | ConcurrentHashMap |
| ILIC ํต๊ณ | O(nยฒ) ๊ฐ๋ฅ | O(n) |
โ 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);
| ํญ๋ชฉ | HashMap | Hashtable | ConcurrentHashMap | LinkedHashMap | TreeMap |
|---|---|---|---|---|---|
| ์์ | ์์ | ์์ | ์์ | ์ฝ์ ์์ | ์ ๋ ฌ |
| 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.
// ๊ธฐ๋ณธ
new HashMap<>(); // capacity 16, loadFactor 0.75
// ํฐ ๋ฐ์ดํฐ
new HashMap<>(1024); // capacity 1024
// ์ธ๋ฐ ์กฐ์
new HashMap<>(1024, 0.6f); // capacity 1024, loadFactor 0.6
๊ณ์ฐ:
new HashMap<>(2_097_152); // ๋ฏธ๋ฆฌ ํ ๋น โ rehashing ํํผ
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 ์๋ ์์ฑ:
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 โ
โ ๋ถ๋ณ ๋ฐ์ดํฐ ํด๋์ค์ ํ์ค.
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;
}
}
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;
}
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()]
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. ๋ค์ ๋
ธ๋ ๋๋ ํธ๋ฆฌ ํ์
// ์ผ๋ฐ์ ์ธ % ์ฐ์ฐ
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๋ฐฐ ๋น ๋ฆ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 (ํ์ ๋นํธ๊ฐ ๋ค๋ฆ)
โ ๋ค๋ฅธ ์ธ๋ฑ์ค์ ๋ถ์ฐ!
static final int TREEIFY_THRESHOLD = 8; // ํ bucket ์ ๋
ธ๋๊ฐ 8๊ฐ ์ด์
static final int MIN_TREEIFY_CAPACITY = 64; // ๊ทธ๋ฌ๋ capacity ๊ฐ 64 ๋ฏธ๋ง์ด๋ฉด rehash ์ฐ์
์กฐ๊ฑด: bucket ์์ ์ฐ๊ฒฐ ๋ฆฌ์คํธ๊ฐ 8๊ฐ ์ด๊ณผ + capacity โฅ 64.
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
ํจ๊ณผ:
static final int UNTREEIFY_THRESHOLD = 6;
resize ํ ํธ๋ฆฌ ๋ ธ๋๊ฐ 6๊ฐ ์ดํ ๊ฐ ๋๋ฉด โ ์ฐ๊ฒฐ ๋ฆฌ์คํธ๋ก ๋ณต๊ท.
(ํธ๋ฆฌ ๊ด๋ฆฌ ๋น์ฉ์ด ๋ ํด ์ ์์ผ๋ฏ๋ก)
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 7):
Java 8 ์ ์๋ฆฌํ ๋ฐฉ์:
(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)
@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));
}
}
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 + "'}";
}
}
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)
)
));
}
}
@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 ์ ๋ง๋ฒ:
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); // ์์
});
}
}
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);
}
}
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());
}
}
// โ ์ํ
public class Customer {
@Override
public boolean equals(Object o) { /* customerId ๋น๊ต */ }
// hashCode() ์ค๋ฒ๋ผ์ด๋ X
}
ํด๊ฒฐ: ๋ ๋ค ์ค๋ฒ๋ผ์ด๋ (๋๋ Record ์ฌ์ฉ).
// โ ์ํ
Map<List<String>, Integer> map = new HashMap<>();
List<String> key = new ArrayList<>();
map.put(key, 1);
key.add("X"); // hashCode ๋ณ๊ฒฝ โ ๋ฉ๋ชจ๋ฆฌ ๋์
ํด๊ฒฐ: ๋ถ๋ณ ๊ฐ์ฒด (String, Integer, ๋ถ๋ณ ํด๋์ค).
// โ ๋ฐ์ดํฐ ์์ + ๋ฌดํ ๋ฃจํ ๊ฐ๋ฅ
Map<String, String> map = new HashMap<>();
ํด๊ฒฐ: ConcurrentHashMap.
// โ O(n)
if (map.containsValue(target)) { ... }
ํด๊ฒฐ: value โ key ์ธ๋ฑ์ค ๋ณ๋ ์ ์ง (์๋ฐฉํฅ ๋งคํ).
// โ ConcurrentModificationException
for (String key : map.keySet()) {
map.remove(key);
}
ํด๊ฒฐ: Iterator.remove() ๋๋ removeIf.
// โ 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์ ๊ฑฐ๋ญ์ ๊ณฑ
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.
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!
}
ํด๊ฒฐ:
[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.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 ๊ตฌํ |
|---|---|
| Java | HashMap |
| Python | dict |
| JavaScript | Map, Object |
| C++ | std::unordered_map |
| Go | map[K]V |
| Rust | HashMap<K, V> |
โ ๊ฑฐ์ ๋ชจ๋ ์ธ์ด์ ํด์ ํ ์ด๋ธ ์กด์ฌ.
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 ์ ๋ชจ๋ ์บ์/์ธ๋ฑ์ค/๊ทธ๋ฃนํ์ ํต์ฌ ๋๊ตฌ.
hash & (n-1))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);
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)
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 ๋ก ์ ํํ ๋ ธ๋ ์๋ณ.
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).
์์ธ ์ค๋ช :
๊ณต๊ฒฉ ์๋๋ฆฌ์ค:
// ์
์์ ์ฌ์ฉ์๊ฐ ๋ชจ๋ ๊ฐ์ hashCode ๋ฅผ ๊ฐ์ง ํค 1๋ง ๊ฐ ์ ์ก
for (int i = 0; i < 10000; i++) {
map.put(maliciousKey(i), value); // ๋ชจ๋ hashCode = 0
}
// HashMap ์ ๊ฒฐ๊ณผ:
// table[0]: Node1 โ Node2 โ Node3 โ ... โ Node10000 (์ฐ๊ฒฐ ๋ฆฌ์คํธ)
๊ฒ์ ๋น์ฉ:
์ค์ ์ฌ๊ฑด:
์กฐ๊ฑด:
์ ํ:
9๋ฒ์งธ ๋
ธ๋ ์ถ๊ฐ ์:
์ฐ๊ฒฐ ๋ฆฌ์คํธ โ Red-Black Tree
ํจ๊ณผ:
๊ฒ์ O(n) โ O(log n)
1๋ง ๊ฐ ๋
ธ๋:
- ์ฐ๊ฒฐ ๋ฆฌ์คํธ: ํ๊ท 5์ฒ ๋ฒ ๋น๊ต
- Red-Black Tree: ์ฝ 13๋ฒ ๋น๊ต (logโ 10000 โ 13)
์ด๋ก ์ ๋ถ์:
์ฆ, 8๊ฐ ์ด์ = "๋น์ ์" ์ํฉ:
โ ๊ทธ๋๋ง ํธ๋ฆฌํ (์ค๋ฒํค๋ ํํผ).
์ ํ์ง:
Red-Black Tree ์ ๊ฐ์ :
resize ํ bucket ์ ๋ ธ๋ ์๊ฐ 6 ์ดํ (UNTREEIFY_THRESHOLD) ๊ฐ ๋๋ฉด:
Tree โ ์ฐ๊ฒฐ ๋ฆฌ์คํธ
์?:
์ถฉ๋์ด ๋ง์๋ capacity ๊ฐ ์์ผ๋ฉด:
if (tab.length < MIN_TREEIFY_CAPACITY) {
resize(); // ํธ๋ฆฌ ๋์ rehash
}
์ด์ : ์์ table ์์ ์ถฉ๋ = capacity ๊ฐ ๋ถ์กฑํ ๊ฐ๋ฅ์ฑ โ rehash ๊ฐ ๋ ํจ๊ณผ์ .
// ๋ชจ๋ ํค๊ฐ ๊ฐ์ 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๋ฐฐ ์ฑ๋ฅ ์ฐจ์ด.
"Treeification ์ HashMap ์ ์์ ๋ง.
์ ์ ์ํฉ์์๋ ๊ฑฐ์ ํธ๋ฆฌํ๋์ง ์์ (8๊ฐ ์ด์ ์ถฉ๋ ํ๋ฅ 1์ต๋ถ์ 1).
๊ทธ๋ฌ๋ HashDoS ๊ณต๊ฒฉ์ด๋ ์๋ชป๋ hashCode ๊ตฌํ ์ ๋๋นํด ์ต์ O(log n) ๋ณด์ฅ.
Java 8+ ์ ๊ฐ์ฅ ์ค์ํ ์ปฌ๋ ์ ๊ฐ์ ์ค ํ๋.
์๋์ด ์ฐจ๋ณํ ํค์๋: TREEIFY_THRESHOLD=8, MIN_TREEIFY_CAPACITY=64, Red-Black Tree, HashDoS."