1. 해시 테이블(Hash Table)
- 해시 테이블은 키와 값을 쌍으로 저장할 수 있는 자료구조입니다.
- 해시 테이블의 가장 큰 특징은 대부분의 연산이 분할 상환 분석에 따른 시간 복잡도가 O(1)이라는 점입니다.
- 덕분에 데이터의 양에 관계 없이 빠른 성능을 기대할 수 있습니다.
1.1 해시 함수(Hash Function)
- 해시 함수란 임의 크기 데이터를 고정 크기 값으로 매핑하는데 사용할 수 있는 함수를 말합니다.
- 해시 테이블의 핵심은 해시 함수입니다.
- 해시 테이블을 인덱싱하기 위해 해시 함수를 사용하는 것을 해싱(Hashing)이라고 합니다.
좋은 해시 함수의 특징
- 해시 함수 값 충돌의 최소화
- 쉽고 빠른 연산
- 해시 테이블 전체에 해시 값이 균일하게 분포
- 사용할 키의 모든 정보를 이용하여 해싱
1.1.1 완전한 해시 함수
- 완전한 해시 함수는 서로 다른 두 개의 키를 해시 함수를 통해 해시 값으로 변경할 때 항상 다른 값을 반환하는 함수를 말합니다.
- 동일하지 않은 어떤 객체 X와 Y가 있을 때, 즉 X.equals(Y)가 '거짓'일 때 X.hashCode() != Y.hashCode()가 같지 않다면, 이때 사용하는 해시 함수는 완전한 해시 함수( perfect hash functions)라고 합니다.
- Boolean 같이 서로 구별되는 객체의 종류가 적거나 Integer, Long 같은 객체는 객체가 나타내는 값 자체를 해시 값으로 사용할 수 있기 때문에 완전한 해시 함수 대상으로 삼을 수 있습니다.
- 하지만 POJO에 대해서는 완전한 해시 함수를 제작하는 것은 사실상 불가능합니다.
- 자바의 HashMap은 기본적으로 각 객체의 hashCode() 메서드를 사용합니다.
- 결과 자료형이 int이기 때문에 32비트 정수 범위 내에서 해시 값이 반환됩니다.
- 32비트 정수 자료형으로는 완전한 해시 함수를 만들 수 없습니다.
- 논리적으로 생성 가능한 객체의 수가 2^32보다 많기 때문입니다.
- 모든 객체에서 O(1)을 보장하기 위해 랜덤 접근이 가능하게 하려면 2^32인 4,294,967,296개의 버킷이 필요합니다.
- 따라서 HashMap을 비롯한 많은 해시 테이블 구현체는 메모리 절약을 위해 해시 함수의 표현 정수 범위보다 작은 M개의 버킷을 사용합니다.
- 즉 완전한 해시 함수를 사용하는 것이 사실상 불가능하고, 충돌이 발생할 수밖에 없습니다.
1.2 충돌(Collision)
- 여러 개의 키를 해시 함수를 통해 해시 값으로 변경할 때 해시 값이 중복될 수 있는데 이를
충돌이라고 합니다. - 서로 다른 두 개의 해시 값이 같은 인덱스를 가리키는 경우도
충돌이라고 합니다. - 비둘기집 원리에 따라 키의 수가 해시 테이블의 크기보다 크다면 반드시 충돌이 일어나게 됩니다.
비둘기집 원리
n개의 아이템을 m개의 컨테이너에 넣을 때, n > m 이라면 적어도 하나의 컨테이너에는 반드시 2개 이상의 아이템이 들어 있다는 원리를 말합니다.
2. 해시 테이블의 구현
- 충돌 발생을 어떻게 처리하는지에 따라 개별 체이닝, 오픈 어드레싱 방식으로 구분됩니다.