Java

[Java] 자료구조 HashMap의 특징 및 핵심 원리

하부루 2024. 9. 18. 12:00

HashMap

  • HashMap 은 키(Key)와 값(value)쌍을 저장하는 자료 구조
  • 각 키는 고유하며, 키를 사용하여 해당하는 값을 빠르게 검색할 수 있음

 

HashMap의 특징

  • 키 기반의 빠른 액세스 : 키를 사용하여 값을 빠르게 검색하거나 수정할 수 있음
  • 순서를 보장하지 않음 : 'HashMap' 은 내부적으로 키의 순서를 보장하지 않음
  • 키의 중복 불가 : 이미 존재하는 키에 대해 값을 저장하면 기존 값이 덮어씌워짐
  • null 키와 값 : 'HashMap'은 null 키와 null 값을 저장할 수 있음 하지만 키는 중복이 불가하므로 null 키는 하나만 저장될 수 있음
  • 키 기반의 유연성 : 어떤 객체든 키로 사용할 수 있음
  • 해싱 충돌두 개 이상의 키가 동일한 해시 코드를 가질 때 충돌이 발생

 

HashMap 주요 메서드

// 새로운 HashMap 생성
	Map<String, Integer> exMap = new HashMap<>();

// HashMap에 데이터 추가
	exMap.put("홍길동", 27);
	exMap.put("김유신", 30);
	exMap.put("김춘추", 42);

	System.out.println("get(홍길동) : " + exMap.get("홍길동"));


// HashMap에 지정된 key값의 vlaue값 삭제
	exMap.remove("김유신");
	
	System.out.println("get(김유신) : " + exMap.get("김유신"));


// HashMap에 지정된 key값이 있는지 확인
	boolean ex1 = exMap.containsKey("김유신");
	boolean ex2 = exMap.containsKey("김춘추");
	
	System.out.println("containsKey(김유신) : " + ex1);
	System.out.println("containsKey(김춘추) : " + ex2);


// HashMap의 크기 (key-value 쌍의 수)
	int size = exMap.size();
	
	System.out.println("exMap.size() : " + size);


// HashMap이 비어있는지 확인
	boolean ex3 = exMap.isEmpty();
	
	System.out.println("exMap.isEmpty() : " + ex3);

// HashMap에 포함된 모든 키를 반환
	Set<String> keys = exMap.keySet();
	
	System.out.println("exMap.keySet() : " + keys);


// HashMap에 포함된 모든 값을 반환
	Collection<Integer> values = exMap.values();
	
	System.out.println("exMap.values() : " + values);

// HashMap의 모든 key-value 쌍을 제거
	exMap.clear();

테스트 결과

 

HashMap 핵심 원리

  • HashMap의 핵심 원리는 해싱(Hashing)
  • Hash란 주어진 입력 데이터를 고정된 크기의 고유한 값(보통 정수)으로 변환하는 과정
  • 해싱 함수(Hashing Function)는 키(Key)를 받아서 정수값인 해시코드(Hashcode)를 반환
  • 반환된 해시코드는 Hash 배열의 각 요소인 버킷(Bucket)의 인덱스가 됨
  • 해시코드(Hashcode)는 HashTable, HashSet, HashMap 같은 자료 구조에서 사용

 

💡 하지만 해시코드를 생성하는 해시 함수는 완벽하지 않기 때문에 키 값이 달라도 해시코드가 동일한 경우가 존재 함

 

해시 충돌 (Hash Collision) 해결 방법

  • 해싱 충돌은 서로 다른 키들이 동일한 해시 값을 가지게 되어 동일한 버킷에 저장될 때 발생
  • 해시 기반 데이터 구조에서는 해시 충돌을 효율적으로 해결하기 위해 여러 가지 방법을 사용

 

[ 체이닝 (Chaining) ]

  • 체이닝은 각 버킷이 자체적으로 하나의 연결 리스트를 가지는 방식
  • 충돌이 발생하면 해당 버킷의 리스트에 새로운 요소를 추가
  • 동일한 해시 값을 가진 요소들은 이 리스트에 추가
  • 충돌이 빈번하게 발생해도 리스트에 요소를 추가할 수 있어, 특정 버킷의 크기에 제한이 없음
  • 많은 충돌이 발생하면 리스트가 길어져 검색 시간이 느려질 수 있음

체이닝을 이용해 같은 해시 값에 여러개의 버킷이 저장되어 있는 경우

get(”강감찬”)을 실행하게 되면 먼저 44498432 해시 값을 가진 [김유신,30]을 찾고

key값이 일치하지 않기 때문에 다음 요소인 [강감찬, 42]를 찾아 반환한다.

 

[ 개방 주소법 (Open Addressing) ]

  • 충돌이 발생할 경우 다른 빈 버킷을 찾아 데이터를 저장하는 방식
  • 별도의 메모리 공간 없이 hash table 내에서 충돌에 대한 처리가 가능
  • key-value 1:1 매칭 구조가 유지
  • 데이터가 늘어나는만큼 미리 bucket에 공간확보가 중요
  • 빈 버킷을 찾는 방법
    • 선형 탐사(Linear Probing) : 충돌이 발생하면 다음 빈 버킷을 순차적으로 찾음
    • 이차 탐사(Quadratic Probing) : 충돌이 발생하면 이차 함수의 간격으로 빈 버킷을 찾음
    • 이중 해싱(Double Hashing ): 두 번째 해시 함수를 사용하여 빈 버킷을 찾음
     

개방 주소법을 이용해 데이터를 조회하는 경우

get(”강감찬”)을 실행하게 되면 먼저 44498432 해시 값을 가진 [김유신,30]을 찾는다.

key값이 일치하지 않기 때문에 탐사법을 사용해 다음 요소들을 순회한다.

key값이 일치하는 [강감찬, 42]를 찾아 반환한다.

 

[ 재해싱 (Rehashing) ]

  • 해시 테이블이 가득 차거나 충돌이 너무 많이 발생할 경우
  • 해시 테이블의 크기를 늘리고 모든 키-값 쌍을 새로운 크기에 맞게 재삽입하는 방법
  • 메모리 사용량을 늘리는 대신 충돌을 줄이고 성능을 향상시키는 데 효과적

 

HashMap 장점

  • 데이터의 순서가 중요하지 않을 때 사용하면 좋음
  • key가 중복되지 않는다는 점을 활용하면 좋음
  • 자료가 많아져도 빠르게 조회가 가능한 장점
  • 키를 기반으로 빠른 데이터 액세스가 필요할 때 사용하면 좋음
  • 어떤 객체든 키로 사용할 수 있다는 장점도 있음