Computer >> 컴퓨터 >  >> 프로그래밍 >> Java

자바(Java) HashMap의 내부 동작 원리 완벽 정리

자바에서 hashCode 함수는 객체의 해시 코드를 얻기 위해 사용됩니다. 이 함수는 모든 클래스의 최상위 부모인 Object 클래스에 정의되어 있으며, 객체 참조의 메모리 주소를 정수 형태로 반환합니다. hashCode는 네이티브(native) 함수로 선언되어 있어, 자바 코드만으로는 객체의 실제 참조 값을 직접 가져올 수 없습니다.

hashCode()와 버킷(Bucket)의 개념

HashMap의 성능을 극대화하려면 hashCode()를 올바르게 활용하는 것이 중요합니다. 기본적으로 이 함수는 해시맵 내부의 버킷(bucket)인덱스(index) 값을 계산하는 데 사용됩니다. 함수는 다음과 같이 정의되어 있습니다.

public native int hashCode();

여기서 '버킷'이라는 용어가 등장했는데, 버킷이 무엇인지 정확히 이해할 필요가 있습니다. 버킷은 노드(node)를 저장하는 요소로, 하나의 버킷에는 두 개 이상의 노드가 존재할 수도 있습니다. 이때 노드들은 연결 리스트(linked list) 자료 구조를 통해 서로 연결됩니다.

HashMap의 전체 용량(capacity)은 버킷 수와 로드 팩터(load factor)를 이용해 계산할 수 있습니다.

용량(Capacity) = 버킷 수 × 로드 팩터(Load Factor)

equals() 함수의 역할

equals 함수는 두 객체 간의 동등성(equality)을 검사하는 데 사용됩니다. 역시 Object 슈퍼 클래스에서 제공되며, 사용자 정의 클래스에서 이를 오버라이드하여 커스텀 구현을 제공할 수 있습니다. 이 함수는 비교 대상인 두 객체가 같으면 true, 다르면 false를 반환합니다.

인덱스(Index) 계산 방법

HashMap은 인덱스 값을 생성함으로써 배열 크기가 불필요하게 커지는 것을 방지하고, 그 결과 OutOfMemoryException 같은 문제를 예방합니다. 배열의 인덱스를 구하는 공식은 다음과 같습니다.

Index = hashCode(key) & (n-1)  // 여기서 n은 버킷의 개수

예제 코드

아래 예제를 통해 실제 동작 과정을 살펴보겠습니다.

import java.util.HashMap;
class hash_map {
   String key;
   hash_map(String key){
      this.key = key;
   }
   @Override
   public int hashCode(){
      int hash = (int)key.charAt(0);
      System.out.println("키에 대한 해시 코드 : " + key + " = " + hash);
      return hash;
   }
   @Override
   public boolean equals(Object obj){
      return key.equals(((hash_map)obj).key);
   }
}
public class Demo {
   public static void main(String[] args){
      HashMap my_map = new HashMap();
      my_map.put(new hash_map("This"), 15);
      my_map.put(new hash_map("is"), 35);
      my_map.put(new hash_map("a"), 26);
      my_map.put(new hash_map("sample"), 45);
      System.out.println("'This' 키의 값 : " + my_map.get(new hash_map("This")));
      System.out.println("'is' 키의 값 : " + my_map.get(new hash_map("is")));
      System.out.println("'a' 키의 값 : " + my_map.get(new hash_map("a")));
      System.out.println("'sample' 키의 값 : " + my_map.get(new hash_map("sample")));
   }
}

실행 결과

키에 대한 해시 코드 : This = 84
키에 대한 해시 코드 : is = 105
키에 대한 해시 코드 : a = 97
키에 대한 해시 코드 : sample = 115
키에 대한 해시 코드 : This = 84
'This' 키의 값 : 15
키에 대한 해시 코드 : is = 105
'is' 키의 값 : 35
키에 대한 해시 코드 : a = 97
'a' 키의 값 : 26
키에 대한 해시 코드 : sample = 115
'sample' 키의 값 : 45

예제 코드 분석

hash_map이라는 이름의 클래스는 문자열 필드와 생성자를 정의합니다. 이후 hashCode 함수를 오버라이드하는데, 여기서는 해시맵의 키 값을 정수로 변환한 뒤 해당 해시 코드를 콘솔에 출력합니다. 키의 첫 번째 문자를 아스키(ASCII) 코드 값으로 변환하여 해시로 사용하는 방식입니다.

다음으로 equals 함수를 오버라이드하여, 입력된 키가 해시맵에 저장된 키와 일치하는지 검사합니다.

Demo 클래스의 main 함수에서는 새로운 HashMap 인스턴스를 생성합니다. put 함수를 사용해 요소들을 해시맵에 추가하고, get 함수로 각 키에 해당하는 값을 조회하여 콘솔에 출력합니다.

주목할 점은 get 호출 시에도 hashCode가 다시 실행된다는 것입니다. 이는 HashMap이 값을 조회할 때도 저장 시와 동일한 방식으로 해시 코드를 계산해 올바른 버킷을 찾아내기 때문입니다. 이처럼 hashCode()equals()가 함께 작동해야 HashMap이 빠르고 정확하게 데이터를 저장하고 검색할 수 있습니다.