1. 해싱이란?
- (알고리즘) 해시 함수는 데이터를 고정된 크기의 해시값, 코드로 변환한다.
- (자료구조) 해시 함수를 활용해 데이터를 저장하고 빠르게 검색한다. (상수시간)
- 해시충돌 : 다른 입력값이 동일한 해시값을 가지는 것을 의미한다.
- 해시충돌 방지 (개방 주소법) : 충돌이 발생하는 경우 다른 버킷에 데이터를 저장한다.
- 해시충돌 방지 (체이닝) : 각 버킷이 연결리스트의 헤더를 가지고 충돌이 발생하면 다음 연결 리스트에 데이터를 저장한다.
2. Java HashMap
- Key:Value 쌍을 구조로 데이터를 저장 및 조회하는 자료구조
- Key:Value 쌍의 개수에 따라 동적으로 크기가 증가하는 Associate Array
- 해시 충돌 방지를 위해 체이닝 기법을 사용한다.
- 기본 버킷 개수는 16이며 해시 충돌이 발생할 때마다 2배씩 증가하여 최대 2^30까지 버킷 갯수를 가질 수 있다.
- 자바8에서는 버킷에 8개 이상의 데이터가 저장되면 트리로 변경되고, 6개 이하인 경우 다시 연결 리스트로 변경된다.
3. HashMap vs HashTable
- JCF(Java Collection Framework)에는 HashMap과 거의 동일한 기능을 제공하는 HashTable이 있다.
- HashTable은 Thread-safe 하여 안전한 대신 속도가 느리다.
- HashMap은 안전하지 않은 대신 속도가 빠르다.
4. 사용법
메소드 | 내용 |
put(K key, V value) | key에 대한 value 값을 맵핑 합니다. |
putIfAbsent(K key, V value) | 해당 key가 없는 경우 value 값을 맵핑합니다. |
putAll( @NotNull Map<? extends K, ? extends V> m ) | 다른 맵으로부터 키-값 쌍을 전부 전달받아 합칩니다. |
ketSet() | 모든 키 값을 Set 자료형으로 반환 받아 순회 시 사용합니다. |
forEach( java.util.function.BiConsumer<? super K, ? super V> action ) | 순차적으로 action을 수행합니다. 각 순회마다 맵의 Key, Value 쌍인 entry를 가져오며 entrySet()과 유사한 기능을 수행합니다. |
@Override public int hashCode() { ,,, } |
Object 클래스의 hashCode 메소드를 재정의합니다. 해시맵에서 객체를 키 값으로 사용하는 경우 해당 객체의 동일성을 재정의한 equals()로 판별하기에 앞서 hashCode()를 확인합니다. 따라서 해시맵에 객체키를 사용하는 경우 hashCode()와 equals() 메소드 모두 재정의가 필요합니다. |
import java.util.Map;
import java.util.HashMap;
public class ch14_HashMap {
public static void main(String[] args) {
Map<String, Integer> hm = new HashMap<>();
hm.put("A", 100);
hm.put("B", 101);
hm.put("C", 102);
hm.put("C", 103); // 1. 이미 값이 있는 경우 값을 덮습니다.
if(!hm.containsKey("D")){
hm.put("D", 104); // 2. 값의 여부를 확인해 덮어쓰기를 방지합니다.
}
hm.putIfAbsent("D", 105);
System.out.println(hm);
System.out.println(hm.get("A"));
System.out.println(hm.get("B"));
System.out.println(hm.get("C"));
System.out.println(hm.get("D"));
System.out.println("========================================");
Map<String, Integer> hm2 = new HashMap<>();
hm2.put("key1", 1);
hm2.put("key2", 2);
hm2.putAll(hm); // 3. hm2 에 hm 의 모든 쌍을 추가해 합침.
System.out.println(hm2);
for(String key : hm2.keySet()){ // 4. keySet() 메소드를 이용해 Map에 있는 모든 키를 순회할 수 있다.
System.out.println("(keySet) " + key + ":" + hm2.get(key));
}
hm2.forEach((key, value) -> { // 5. forEach() 메소드와 람다식을 활용해 모든 키, 값을 순회할 수 있습니다.
System.out.println("(forEach) " + key + " : " + value);
});
System.out.println("========================================");
// 객체키 : equals() 메소드 오버라이딩
Person p1 = new Person("1", "111111-1111111");
Person p2 = new Person("2", "222222-2222222");
Person who = new Person("1", "111111-1111111");
Map<Person, Integer> hm3 = new HashMap<>();
hm3.put(p1, 0);
hm3.put(p2, 1);
System.out.println("Does map include " + who.getName() + "? => " + hm3.containsKey(who));
hm3.put(who, 3);
System.out.println(hm3);
}
}
class Person{
private String name;
private String id;
public Person(String name, String id){
this.name = name;
this.id = id;
}
public String getName() {
return this.name;
}
@Override
public String toString(){
return this.name;
}
@Override
public boolean equals(Object o){ // Object 클래스는 최상위 객체이다.
if(o instanceof Person){ // 동일한 Person 객체이며
Person p = (Person) o; // Person 으로 형변환
return this.id.equals(p.id) && this.name.equals(p.name); // 필드값이 같은 경우 동일한 객체로 간주한다.
}
return false; // 다른 클래스인 경우 false를 반환.
} // => equals 오버라이딩으로 같은 객체임을 명시함에도 불구하고 해시맵에서 인식하지 못한다. => 어떻게 해야할까?
// HashMap은 hashCode()를 비교하고 같은 경우 equals()를 수행한다.
@Override
public int hashCode(){
return name.hashCode() + id.hashCode(); // id와 name으로 해시코드를 만들어 주어 id와 name이 동일한 경우 같은 해시 값을 반환하도록 한다.
} // 따라서 반드시 같은 객체를 판단하는 equals를 재정의할 때 함께 hashCode도 재정의 해주어야 합니다.
}
5. 추가
- 왜 Map 인터페이스를 선언하고 HashMap을 생성하는가?
=> 객체지향 설계 원칙에 따라 Map 인터페이스로 생성할 때에 다음과 같은 이점이 있습니다.
다형성 : Map 인터페이스 변수를 사용함으로서 필요에 따라 다른 구현체들을 쉽게 사용할 수 있습니다. 예를 들어, 코드를 나중에 수정할 때, LinkedHashMap, TreeMap, HashMap, HashTable 등으로 기존 코드를 수정하지 않고도 교체할 수 있습니다.
유연성 : 인터페이스를 사용하는 것은 구현체와의 결합도를 낮추어 유지보수와 확장성을 높입니다.
테스트 용이성 : Map 인터페이스를 사용하면 단위 테스트 시 Mock 개겣를 사용하는 것이 더 쉬워집니다. 예를들어, Map 인터페이스를 사용하면 HashMap 대신 테스트를 위한 MockMap을 사용할 수 있습니다.
따라서 인터페이스 Map을 활용해 HashMap을 생성하는 것이 다형성, 유연성을 활용하는 좋은 프로그래밍 습관입니다.
'프로그래밍언어 > Java' 카테고리의 다른 글
자바 쓰레드 (0) | 2023.07.26 |
---|---|
Java Lambda Expression, 자바8 람다식 (0) | 2023.06.21 |
Java Collection Framework (JCF) (0) | 2023.06.21 |
람다식 (0) | 2023.05.24 |
가변객체와 불변객체 (0) | 2023.05.23 |