데이터를 가장 빠르게 찾을 수 있는 방법은 무엇일까? 그것은 ‘해시맵(HashMap)‘이다. 해시맵은 배열처럼 데이터가 촘촘히 모여있지 않아도, 원하는 값을 상수 시간 O(1) 안에 찾아낼 수 있는 자료구조다.
핵심 구조
해시맵은 본질적으로 ‘키(Key)‘를 ‘배열의 인덱스(Index)‘로 매핑하는 구조이다.
- 해시 함수(Hash Function): 키값을 입력받아 배열의 인덱스로 변환한다.
- 버킷(Bucket) 배열: 데이터를 저장하는 물리적 공간이다.
- 충돌 해결(Collision Resolution): 같은 인덱스에 데이터가 들어오려 할 때 이를 해결하는 전략(체이닝 등).
직접 구현해보는 HongjaeMap
자바의 HashMap 라이브러리를 쓰지 않고, 내부 원리를 그대로 따르는 HongjaeMap을 직접 구현해보겠습니다.
public class HongjaeMap<K, V> {
// 내부 데이터 저장용 노드 클래스
private static class Node<K, V> {
K key;
V value;
Node<K, V> next;
Node(K key, V value) {
this.key = key;
this.value = value;
}
}
private Node<K, V>[] buckets;
private int capacity = 16; // 기본 용량
@SuppressWarnings("unchecked")
public HongjaeMap() {
buckets = new Node[capacity];
}
// 해시 함수: 키의 hashCode를 배열 크기로 나머지 연산
private int getBucketIndex(K key) {
return Math.abs(key.hashCode()) % capacity;
}
// 데이터 삽입
public void put(K key, V value) {
int index = getBucketIndex(key);
Node<K, V> head = buckets[index];
// 키가 이미 존재하면 값 업데이트
while (head != null) {
if (head.key.equals(key)) {
head.value = value;
return;
}
head = head.next;
}
// 키가 없으면 새로운 노드를 만들어 앞에 추가 (Chaining)
Node<K, V> newNode = new Node<>(key, value);
newNode.next = buckets[index];
buckets[index] = newNode;
}
// 데이터 조회
public V get(K key) {
int index = getBucketIndex(key);
Node<K, V> head = buckets[index];
while (head != null) {
if (head.key.equals(key)) {
return head.value;
}
head = head.next;
}
return null;
}
}
구현의 핵심 포인트
Node클래스: 해시 충돌(같은 인덱스) 발생 시 데이터를 연결 리스트(Chaining) 방식으로 묶기 위해next포인터를 포함합니다.getBucketIndex: 자바의hashCode()를 사용하여 키를 배열의 인덱스로 변환합니다.Math.abs를 사용하는 이유는 해시 코드가 음수일 경우를 방지하기 위함입니다.put메서드: 같은 키가 있는지 리스트를 순회하며 확인합니다. 있다면 값을 갱신(Update)하고, 없다면 새로운 노드를 리스트의 맨 앞에 삽입합니다.get메서드: 인덱스를 계산하여 해당 버킷의 연결 리스트를 탐색하고 키가 일치하는 값을 반환합니다.
이처럼 HongjaeMap은 자바 표준 라이브러리 없이도 해시맵의 근본 원리인 인덱스 직접 계산과 연결 리스트를 통한 충돌 해결을 완벽하게 수행합니다.