내가 만든 자바 유틸 “LinkedMap”

Map형태의 자바 객체를 소개하고자 한다.
HashMap은 key/value형태의 자료구조를 다루는 자바 뿐만 아니라 모든 프로그램에서 거의 예외 없이 사용하는 클래스 이다. 언젠가 다시 C프로그램을 해야하는 상황이 되었는데 HashMap형태의 자료구조가 없어서 힘들었던 기억이 있다.

그런데 자바의 HashMap으로 충분한가? 불편한 점은 없는가?

자바에는 java.util.* 에서 java.util.Map을 구현한 클래스들은 크게보면 HashMap(Hashtable), TreeMap, LinkedHashMap 정도가 있다. (자세한 사항은 각자 검색)

아래와 같은 프로그램하면 HashMap/TreeMap/LinkedHashMap의 차이를 알 수있다.

<코드샘플>

java.util.Map<String, Integer> map = new HashMap<String, Integer>();
map.put(“B”, 200);
map.put(“A”, 300);
map.put(“C”, 100);
map.put(“D”, 400);
System.out.println(map);

<결과 출력>
HashMap => {D=400, A=300, B=200, C=100} : 순서알수 없음
TreeMap => {A=300, B=200, C=100, D=400} : 키 정렬
LinkedHashMap => {B=200, A=300, C=100, D=400} : 입력된 순서
그런데 HashAccess가 가능한 FIFO 큐를 만들고자 할때 우리는 LinkedList + HashMap을 하나의 클래스에 묶어서 만들게 된다. 본문에서는 LinkedList+HashMap을 하나의 클래스로 구현한 LinkedMap이라는 클래스를 소개하고자 한다.

입력 순서 보장

LinkedMap은 입력 순서를보장한다. 이것은 상당한 매력이 있다. 예를 들어 데이터를 조회하면서 Map에 값들을 등록하면 순서가 뒤바뀌어서 난감한 경우가 많다 그렇다고 TreeMap을 사용하면 정렬 되어버린다.
LinkedMap은 입력 순서로 Enumeration을 통해 값을 조회할 수 있다.

LinkedMap => {B=200, A=300, C=100, D=400} : 입력된 순서로 출력

그런데 이것은 앞서 보여준 자바의 LinkedHashMap과 동일하다 하지만 LinkedMap에는 removeFirst() 와 removeLast() 가 있어 보다 유연하게 로직을 구현할 수 있다.
아래와 같은 구현도 가능하다.

LinkexMap m=…
while(m.size() > 0){
Object value = m.removeFirst();

}

Hash Access + Queue 구현체

LinkedMap은 기본적으로 Map형태의 구조체이면서 순서 기능이 있어 선입선출 큐로 활용가능하다.
또한 최대 크기를 지정할 수 있음으로 보다 안전한 코드(overflow방지)를 작성할 수 있다.

스크린샷 2016-04-08 오후 12.04.22

큐에서 순서대로 꺼내거나 키를 사용하여 직접 조회/삭제 할 수 있다.

선입/후입 – 선출/후출

putFirst/putLast를 사용하여 전체 엔트리에서 앞쪽에 입력하거나 뒷쪽에 입력할 수 있다.

스크린샷 2016-04-08 오후 12.05.15

put은 이미 존재하는 값은 업데이트하고 없으면 엔트리의 마지막 순서에 값을 입력한다. 하지만 putFirst/putLast는 이미 입력된 값까지도 순서를 바꿔버린다.

이러한 기능들은 통해 LinkedMap 사용자는 쉽게 케쉬를 구현할 수 있다.

최대크기 보장과 오버플로어 핸들러

자바에서 메모리 릭을 유발하는 대부분의 경우는 Map이나 List 객체에서 발생한다. 삭제로직 버그로인해 엔트리 크기가 계속 증가하는 것이다.
LinkedMap은 최대 크기를 지정할 수있다.

setMap(int)

그런데 생성자에서 크기를 지정하지 않고 별도 메소드를 사용하는 이유는 런타임에 객체의 최대 사이즈를 변경하기 위해서서
예를 들어 LinkedMap을 사용하여 직접접근이 가능한 큐로 활용하고 있을때 최대 크기를 런타임에 수정할때 새로운 객체를 만들고 옮겨 담아야 한다면 불편한점이 발생한다.

그런데 이렇게 최대 사이즈상태에 도달하여 어떤값이 예상치 못하게 삭제 된다면 적절한 처리(로깅 같은)를 하고자 할 수 있다. 이때 필요한 것이 overflow핸들러다.

LinkedMap<String, Integer> map = new LinkedMap<String, Integer>() {
    @Override
    protected void overflowed(String key, Integer value) {
        System.out.println(key + ” is overflowed!!”);
    }
}.setMax(10);
위 샘플은 최대 크기가 10으로 지정되어있으며 그이상 들어올때는 key를 출력하는 샘플이다.

조회 시 없으면 더미 값를 추가하는 기능

Map 을 사용할때 get()을 호출한후 값이 없으며 새로운 값을 입력해야하는 경우가 많이 있다.

Map<String, List> map1 = new HashMap<String, List>();
List children = map1.get(“paul”);
if(children==null){
    children = new ArrayList();
    map1.put(“pau;”,children);
}
children.add(“channy”);

위에서 보면 get()호출하고 값이 없으면 put()을 다시 호출한다. 코드 라인도 길어지고 get과 put이 중복의 의미가 있기때문에 ListMap은 이것을 하나의 함수로 제공한다.

LinkedMap<String, List> map2 = new LinkedMap<String, List>() {
    protected List create(String key) {
        return new ArrayList();
    }
};
map2.intern(“paul”).add(“channy”);

LinkedMap에서의 사례를 보면 intern()이라는 함수로 통합하였다. intern에 키를 넣고 조회하여 만약 값이 없으면 내부에서 create()가 호출된다. create()가 리턴한 값은 저장되고 intern()에 리턴된다. create 가 어떤 더미를 생성해야하는지를 알기위해 재정의 해주어야 한다.

원래의 create함수는 RuntimeException을 throw하고 있기때문에 create를 재정의하지 않은 상태에서 intern()을 호출하면 RuntimeException이 throw된다.

결론

LinkedMap 은 크기제한, 순서유지(추가/삭제), 조회시 더미생성처럼 이전에 hashmap을 사용함에 불편한 사항들을 추가하였다.

첨부파일에서 http://tech.whatap.io/wp-content/uploads/2016/04/LinkedMap.txt 에서 소스를 확인할 수 있다.

https://github.com/scouter-project/scouter/ 에서도 scouter.util.LinkedMap(추가 수정 예정)을 찾을 수 있다.