본문으로 건너뛰기
김신건의 로그

[Java] HashMap

· 수정 · 📖 약 2분 · 697자/단어 #java #collection #map #hash #jcf
HashMap, java.util.HashMap, Java HashMap, 해시맵

정의

java.util.HashMap<K,V>해시 테이블 로 구현된 Map. 평균 O(1) 의 put/get/remove 를 보장하고 단일 스레드 환경의 사실상 표준.

JDK 1.2 도입, Java 8 에서 충돌 시 linked list → red-black tree 로 자동 변환 하는 큰 개선이 있었다.

시각화

내부 구조

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

    transient Node<K,V>[] table;          // 버킷 배열
    transient int size;
    int threshold;                         // capacity * loadFactor
    final float loadFactor;                // 기본 0.75

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;                    // chaining (linked list)
    }
}
  • table: 버킷 배열, 기본 길이 16, 2^n 형태로 grow
  • Node: linked list 의 노드
  • Java 8+: 한 버킷의 노드가 8 개 이상 이면 TreeNode (red-black tree) 로 변환 → 64 미만 이면 다시 list

put 의 흐름

  1. hash = key.hashCode() 후 spread (상위 16비트 XOR)
  2. index = (table.length - 1) & hash
  3. 버킷이 비어 있으면 → 새 Node 삽입
  4. 같은 key 가 있으면 → 값 교체
  5. 충돌 → linked list 끝에 append (또는 tree 에 insert)
  6. size > threshold → resize (2x grow + rehash)
public V put(K key, V value) {
    int h = spread(key.hashCode());
    int i = (table.length - 1) & h;
    Node<K,V> first = table[i];
    if (first == null) {
        table[i] = new Node<>(h, key, value, null);
    } else {
        // chain 순회, 같은 key 발견 시 교체
        // 끝에 도달하면 append, 8 개 이상이면 treeify
    }
    if (++size > threshold) resize();
    return null;
}

충돌 처리, chaining 과 treeify

같은 버킷에 여러 key 가 매핑되면 (해시 충돌):

  • TREEIFY_THRESHOLD = 8: 한 버킷에 8 노드 이상이면 tree 로 변환 (단, table 크기가 64 이상일 때만)
  • UNTREEIFY_THRESHOLD = 6: tree 가 6 노드 이하로 줄면 다시 list
  • MIN_TREEIFY_CAPACITY = 64: table 이 작을 때는 resize 가 우선

tree 로 변환되면 그 버킷의 최악 검색이 O(log n). 악의적 hash collision (수많은 입력이 같은 hash) 으로부터 보호.

복잡도

작업평균최악 (Java 8+)최악 (Java 7 이전)
get, put, removeO(1)O(log n)O(n)
containsValueO(n)O(n)O(n)
iterator 순회O(n + capacity)같음같음

containsKey 의 평균이 O(1) 인 이유는 해시 충돌이 적기 때문. 충돌이 폭주하지 않으려면 equals/hashCode 가 잘 분포해야 한다.

load factor 와 resize

  • loadFactor (기본 0.75): size / capacity 가 이를 초과하면 resize
  • resize(): table 길이를 2 배로 늘리고 모든 entry 를 새 위치로 rehash. O(n) 비용.

생성 시 초기 capacity 지정으로 미리 줄일 수 있다.

// 1000 개 entry 예상 → capacity 가 1000 / 0.75 ≈ 1334 보다 커야 resize 안 함
Map<String, Integer> map = new HashMap<>(2048);

null 허용

HashMapkey 와 value 모두 null 허용. key 가 null 이면 항상 버킷 0 에 매핑.

map.put(null, 1);              // OK
map.put("a", null);            // OK
map.get(null);                 // 1

ConcurrentHashMap 과의 가장 큰 차이.

함정

1. 가변 객체를 key 로 쓰면 안 됨

Map<List<Integer>, String> map = new HashMap<>();
List<Integer> key = new ArrayList<>(List.of(1, 2));
map.put(key, "v");
key.add(3);                      // hashCode 변경
map.get(key);                    // null! (다른 버킷으로 가게 됨)

불변 객체 (String, Integer, Long, Enum, record) 를 key 로 사용 권장.

2. equals 만 override 하고 hashCode 안 함

Object 의 규약 위반. HashMap 이 조용히 잘못 동작.

3. 동시 수정

HashMap 은 thread-safe 가 아니다. 동시 put 으로 무한 루프 가 발생한 사례가 유명 (특히 Java 7 의 resize 중 chain 역전 버그). ConcurrentHashMap 사용.

4. iterator 는 fail-fast iterator

순회 중 수정 → ConcurrentModificationException.

HashMap vs Hashtable vs ConcurrentHashMap

항목HashMapHashtableConcurrentHashMap
Thread-safe✓ (메서드)✓ (bin-level)
null key/value
도입JDK 1.2JDK 1.0 (legacy)JDK 1.5
성능가장 빠름느림 (전체 락)동시성 최적
권장단일 스레드절대 X동시성

참고

이 글의 용어 (9개)
[Java] Collectionjava
정의 는 그룹으로 묶인 객체들을 표현하는 최상위 인터페이스. JCF (Java Collections Framework) 의 입구이자, / / / 모두 이를 확장한다. 자체는 직접…
[Java] ConcurrentHashMapjava
정의 는 고동시성 환경에서 사용 가능한 구현. JSR-166 (Java 5) 도입, Java 8 에서 내부 구조가 크게 재작성됐다. 과 같은 인터페이스를 제공하면서 thread-…
[Java] ConcurrentModificationExceptionjava
정의 (CME) 는 가 컬렉션의 구조 변경을 탐지했을 때 던지는 unchecked 예외 ( ). 이름과 달리 반드시 동시성 (멀티스레드) 문제일 때만 발생하는 것이 아니다. 단일…
[Java] Fail-Fast Iteratorjava
정의 Fail-Fast Iterator 는 순회 도중 컬렉션의 구조가 변경되면 즉시 을 던지는 iterator. , , 등 대부분의 컬렉션의 iterator 가 이 방식. 목적은…
[Java] Iterablejava
정의 는 루프로 순회 가능한 모든 타입의 최상위 인터페이스. 단 하나의 추상 메서드, 를 정의한다. 인터페이스가 을 extends 하므로 , , , 등 모든 컬렉션이 자동으로 대…
[Java] LinkedHashMapjava
정의 는 을 상속해 삽입 순서 (또는 접근 순서) 를 유지 하는 Map. 모든 의 평균 O(1) 성능을 유지하면서 추가로 순회 순서가 일관된다. 핵심: 이중 연결 리스트 (dou…
[Java] Objectjava
정의 는 Java 의 모든 클래스의 최상위 부모 (root) 클래스. 가 명시되지 않은 클래스는 컴파일러가 자동으로 를 붙인다. 인터페이스는 클래스가 아니라 를 직접 상속하지는 …
[Java] TreeMapjava
정의 는 Red-Black Tree 기반의 정렬된 . (확장 인터페이스) 을 구현해 범위 쿼리, floor/ceiling, sub-map 같은 정렬 기반 연산을 제공한다. key…
집합과 맵 (Set and Map)algorithm
정의 집합 (Set) 은 고유한 원소들의 모임을 나타내는 추상 자료구조로, 멤버십 검사, 삽입, 삭제를 지원한다. 맵 (Map, Dictionary) 은 키-값 쌍을 저장하며, …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기