Java의 다양한 Map 관련 클래스

Written by kwSeo

#다양한 Map 클래스 Java는 다양한 Map 관련 클래스를 제공한다. 여기에서는 Map 관련 클래스 중 몇가지를 간단히 살펴본다.

#Map Interface Map 자료구조는 키와 값의 쌍으로 이루어진 구조로, Dictionary로도 불린다.(그런데 Java에는 Map과 Dictionary가 따로따로 존재한다. Dictionary는 초창기에 구현된 구식 모듈이라 사용을 권장하지 않는다 Dictionary를 대체한 것이 Map이다) 값을 중복될 수 있지만, 키는 유일한 식별자로서 중복되면 안된다. Java에는 다양한 Map 관련 클래스가 존재한다. Map과 관련된 클래스가 구현하고있는 인터페이스를 따라가면 최상위에는 항상 Map 인퍼페이스가 존재한다. Map인터페이스는 Map<K, V>와 같이 선언되어있고, K는 키를, V는 값을 의미한다. 지금부터 Map 인터페이스를 구현하고 있는 대표적인 Map 클래스를 몇 가지 살펴보자.

#HashMap 해시(Hash)라는 말을 꽤나 자주 보는 거 같다. 해시는 해시 함수를 통해 임의의 길이를 가진 입력값을 고정된 길이의 결과값으로 변환해준다. 이점을 이용해서 구현된 Map이 바로 HashMap이다. HashMap 역시 내부적으로 해시 함수를 구현하고 있다. 임의 값인 키의 해시코드를 Hash Function을 통해 고정된 정수형 값으로 매핑한다. 고정된 정수형 값은 범위를 가지고 있으며, 사용자가 지정할 수도 있다. 이 범위가 바로 키값쌍이 저장될 수 있는 개수이며, 기본값은 16이다. 값이 늘어남에 따라 부하계수를 측정한다. 부하계수를 간단히 말하자면 HashMap이 가지고 있는 키값쌍의 수와 최대 키 개수의 비율로 생각하면 된다. 부하계수가 특정 수치를 넘어가면 2배씩 용량(값의 범위)이 늘어난다. 역시 이 부하계수도 사용자가 지정할 수 있으며, 기본값은 0.75이다. 상황에 따라서 메모리를 많이 차지할 수 있지만, 매우 빠른 접근 속도를 자랑한다.

Map<String, String> map = new HashMap<>();				//기본값으로 생성
Map<String, String> map2 = new HashMap<>(11, 0.9);	//용량: 11, 부하율: 0.9으로 생성
map.put("A", "AAA");
map.put("B", "BBB");
String value = map.get("A");

그런데 Hash하면 항상 따라오는 것이 바로 충돌이다. HashMap은 내부적으로 보조 해시 함수를 정의하여 이를 해결하고 있다. HashMap이 선택한 방법은 Separate Chaining이다. 이 방법은 서로 다른 키로 부터 같은 해식값이 나왔을 경우, 이를 리스트로 관리하는 것이다. 자세한 것은 구글링을 해보자. Java8부터는 이 부분이 개선되었다고 한다. 보조 해시 함수가 이전보다 단순해지고, 리스트가 트리로 바뀌었다고 한다.

HashMap은 키와 값들의 순서를 보장하지도 않는다. 또한 동기화도 지원하지 않기 때문에, 동기화를 위해서는 HashTable을 사용하거나 아래와 같은 방법을 사용해야한다.

Map<String, String> map = Collections.synchronizedMap(new HashMap<>()); 

#TreeMap TreeMap은 또한 Map 인터페이스를 구현하고 있는 클래스로 사용법 역시 기존의 Map 관련 클래스와 동일하다. TreeMap의 특징이라면 이름 그대로 트리를 사용해서 구현되었다는 것이다. 모든 키 값 씽은 키를 기준으로 해서 트리 구조로 관리된다. 이때 사용한 트리는 Red-Black 트리이다. 그래서 HashMap과는 달리 충돌 같은 것들을 걱정할 필요도 없고, 순서 또한 보장된다. 트리의 특성상 키를 기준으로 해서 정렬이 되기 때문이다. 또한 O(log n)이라는 매우 빠른 속도를 보인다(HashMap보다는 느리다).

Map<String, String> map = new TreeMap<>();
map.put("A", "AAA");
map.put("B", "BBB");
String value = map.get("A");

그런데 TreeMap 역시 동기화를 지원하지 않기 때문에, 동기화를 위해서는 HashMap처럼 아래와 같은 방식으로 사용해야한다.

Map<String, String> map = Collections.synchronizedMap(new TreeMap<>()); 

#LinkedHashMap LinkedHashMap은 HashMap을 상속받은 클래스이다. 그렇기 때문에 전체적으로 HashMap과 동일하지만, 모든 키값쌍이 링크드리스트처럼 연결되어 있다는 특징이 있다. 그렇게 때문에 키값쌍들의 순서가 보장되며, 이때 순서는 입력된 순서를 말한다.

Map<String, String> map = new LinkedHashMap<>();
map.put("A", "AAA");
map.put("B", "BBB");
String value = map.get("A");  

또한 LinkedHashMap은 특별한 메서드를 제공한다. removeEldestEntry라는 메서드는 사용자가 오버라이딩해서 사용할 수 있도록 구현되어 있다. LinkedHashMap클래스는 put, putAll 등의 메서드를 통해 새로운 키값쌍이 추가된 후 내부적으로 자동으로 removeEldestEntry메서드를 호출한다. 사용자는 이 메서드를 오버라이딩하여 자신만의 정책을 정의할 수 있다. 이 메서드가 true을 반환하는 경우 가장 오래된(가장 먼저 추가되었던) 키값쌍이 삭제된다. removeEldestEntry메서드를 오버라이딩하지 않는 경우, 이 메서드는 아무 작업도 없이 그저 false만을 반환하며, LinkedHashMap은 일반 HashMap과 다름없이 작동한다.

Map<String, String> map = new LinkedHashMap<String, String>(){
	private static final int MAX = 137;
	
	@Override
	protected boolean removeEldestEntry(Entry<String, String> eldest) {
		return size() > MAX;
	}
};

참고로 이 클래스 역시 동기화를 제공하지 않는다. 그렇기 때문에 TreeMap이나 HashMap과 같은 방법을 사용해야한다.

Map<String, String> map = Collections.synchronizedMap(new LinkedHashMap<>());

Done At: Sep 16,2015
Categories: