Map(HashMap, TreeMap)


Map


영어단어 map의 뜻이 뭐냐고 아무사람에게 물어본다면

열에 아홉은 ‘지도’라고 답할 것입니다.

물론 ‘지도’도 맞지만 다른 뜻도 있습니다.

예를 들어 mapping 이라는 뜻은 matching과 뜻이 비슷합니다.

자바에서 이 자료구조를 Map이라고 이름지은 정확한 이유는 모르지만

여튼 match와 비슷하다고 보면 됩니다.


Map에 저장되는 데이터는 ‘key-value’ pair라는 형식을 갖고 있습니다.

key와 value는 매칭되어 있죠.

이는 ‘주민등록번호 - 사람이름’ 관계와 비슷하다고 보면 됩니다.

주민등번호는 한명도 똑같은 사람이 없죠. -> key는 중복 X

주민등록번호는 있는데 사람 이름이 없는 경우는 없죠. -> key없는 value는 없음

이름이 있는 사람이 주민등록번호가 없는 경우는 없죠. -> value없는 key는 없음

주민등록번호가 달라도 사람이름은 똑같을 수 있죠.(동명이인) -> value는 중복 가능


Map에서 특정 데이터를 찾을 때는 key를 이용해서 검색합니다.

즉, 주민등록번호로를 입력하면 그에 매칭되는 사람의 이름을 알 수 있다는 말입니다.

API에서 Map의 메소드를 찾아보면 다음과 같습니다.

java_map_method

이 중 가장 많이 쓰이는 건 put(), get(), remove()입니다.

예를 들어 map이란 Map의 객체가 있고, 그 안에 한국사람들의

주민번호-이름 데이터가 저장되어 있다면

map.get(“890716-1XXXXXX”)

은 “Onsil”이라는 value값을 리턴합니다.

자세한 메소드 사용은 밑에서 알아보겠습니다.


Map은 인터페이스로 선언되어 있고, Map을 구현한 여러 클래스들이 있습니다.

API문서에서 All Known Implementing Classes를 보면 알 수 있죠.

java_map_api

이 중 가장 많이 쓰이는 클래스는 HashMap, TreeMap, LinkedHashMap 입니다.

여기서는 HashMap에 대해서 자세히 알아보겠습니다.

넘어가기 전에 HashMap과 HashTable에 대해 간단히 살펴보겠습니다.

둘의 차이점은 다음과 같습니다.


기능 HashMap HashTable
키나 값에 null 저장 가능 여부 O X
여러 쓰레드 안전 여부 X O


이를 제외하면 이 둘은 거의 비슷합니다.

그래서 HashMap을 Thread safe하게 이용하려면 다음과 같이 선언해야만 합니다.

Map m = Collections.synchronizedMap(new HashMap(…));

이에 대해서는 나중에 더 자세히 다루겠습니다.




HashMap


java_hashmap_constructor

HashMap 클래스의 생성자입니다.

생성자에 보면 capacity와 load factor라는 용어가 나옵니다.

이에 대한 자세한 설명은 »여기«에 있습니다.

일단 초보개발자에게 중요한 건, 담을 데이터 수가 많으면

2번째 생성자 사용해서 초기 저장 공간을 크게 설정해주는게 좋다는 것입니다.


MapSample.java

import java.util.Collection;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;

public class MapSample {
    public static void main(String[] ar){
        MapSample ex = new MapSample();
        ex.checkHashMap();
    }

    public void checkHashMap(){
        Map<String, String> map1 = new HashMap<>();
        map1.put("a", "A");
        map1.put("b", "B");
        System.out.println("map1 = " + map1);
//        map1 = {a=A, b=B}

        System.out.println("map1.get(\"a\") = " + map1.get("a"));
//        map1.get("a") = A

        System.out.println("map1.get(\"A\") = " + map1.get("A"));
//        map1.get("A") = null
        map1.put("b", "BitCoin");
        System.out.println("map1 = " + map1);
//        map1 = {a=A, b=BitCoin}

        Set<String> keySet = map1.keySet();
        System.out.println("KeySet = " + keySet);
//        KeySet = [a, b]

        System.out.println();
        for(String tempKey: keySet){
            System.out.println("map1.get(\"" + tempKey + "\") = " + map1.get(tempKey));
        }
//        map1.get("a") = A
//        map1.get("b") = BitCoin

        System.out.println();
        Collection<String> values = map1.values();
        System.out.println("values = " + values);
//        values = [A, BitCoin]
    }
}

checkHashMap()을 보면

map1이라는 객체를 만들고,

put()메소드를 이용해 key-value 를 집어넣었습니다.


get()메소드를 이용해 key에 해당하는 value값도 출력하고 있습니다.

해당 key가 없을 때는 null이 리턴되네요.


“b”-“B” 데이터가 이미 있는데 새로 “b”-“BitCoin” 데이터를 넣으니

key가 같으니 value값은 덮어쓰기가 됩니다.


keySet() 메소드는 해당 Map에 key들만 모아 Set으로 리턴합니다.

values() 메소드는 해당 Map에 value들만 모아 Collection으로 리턴합니다.

다음 예제를 살펴보겠습니다.

MapSample2.java

import java.util.HashMap;
import java.util.Set;
import java.util.Map;

public class MapSample2 {
    public static void main(String[] ar){
        MapSample2 ex = new MapSample2();
        ex.checkHashMap();
    }

    public void checkHashMap(){
        HashMap<String, String> map = new HashMap<>();
        map.put("a", "A");
        map.put("b", "B");
        map.put("c", "C");
        map.put("d", "D");

        Set<Map.Entry<String, String>> entries = map.entrySet();
        System.out.println("entries = " + entries);
//        entries = [a=A, b=B, c=C, d=D]

        for(Map.Entry<String, String> tempEntry: entries){
            System.out.println("tempEntry.getKey() = " + tempEntry.getKey() + ", tempEntry.getValue() = " + tempEntry.getValue());
        }
//        tempEntry.getKey() = a, tempEntry.getValue() = A
//        tempEntry.getKey() = b, tempEntry.getValue() = B
//        tempEntry.getKey() = c, tempEntry.getValue() = C
//        tempEntry.getKey() = d, tempEntry.getValue() = D

        System.out.println();

        System.out.println("map.containsKey(\"d\") = " + map.containsKey("d"));
        System.out.println("map.containsKey(\"z\") = " + map.containsKey("z"));
        System.out.println("map.containsValue(\"B\") = " + map.containsValue("B"));
        System.out.println("map.containsValue(\"T\") = " + map.containsValue("T"));
//        map.containsKey("d") = true
//        map.containsKey("z") = false
//        map.containsValue("B") = true
//        map.containsValue("T") = false
    }
}

이번엔 HashMap 객체로 map을 만들어봤습니다.

entrySet()이라는 메소드는 출력결과에서 볼 수 있듯이

해당 객체의 데이터를 entrySet element로 가지는 Set을 리턴합니다.


Map.Entry 타입은 getKey()와 getValue()를 통해

key와 value값을 리턴받을 수 있습니다.


또한 containsKey()와 containsValue()를 이용해

해당 값이 map 객체 안에 있는지 확인하고 있습니다.

있으면 true, 없으면 false를 리턴합니다.

다음 예제를 살펴보겠습니다.


MapSample3.java

import java.util.HashMap;
import java.util.Map;

public class MapSample3 {
    public static void main(String[] ar){
        MapSample3 ex = new MapSample3();
        ex.checkHashMap();
    }

    public void checkHashMap(){
        Map<String, String> map = new HashMap<>();
        map.put("a", "A");
        System.out.println("map = " + map);
//        map = {a=A}

        map.remove("a");
        System.out.println("map = " + map);
//        map = {}
        System.out.println("map.size() = " + map.size());
//        map.size() = 0
    }
}

이번 예제는 remove()와 size()메소드를 보여주기 위함입니다.

remove()를 하면 해당 key에 해당하는 key-value 데이터가 삭제됩니다.

삭제하고 나서 size()메소드를 통해 map객체의 크기를 살펴보는데

당연히 저장된 데이터가 없으므로 0이 리턴되네요.




TreeMap


HashMap은 데이터의 정렬이라는게 없습니다.

그런데 개발하다보면 데이터의 key를 기준으로 정렬할 경우가 생기는데

이 때 유용하게 쓸 수 있는게 TreeMap 클래스입니다.

참고로 정렬 기준은 “숫자 > 알파벳 대문자 > 알파벳 소문자 > 한글” 입니다.

예제를 살펴보겠습니다.

TreeMapSample.java

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class TreeMapSample {
    public static void main(String[] ar){
        TreeMapSample ex = new TreeMapSample();
        ex.checkTreeMap();
    }

    public void checkTreeMap(){
        TreeMap<String, String> map = new TreeMap<>();
        map.put("a", "A");
        map.put("b", "B");
        map.put("1", "one");
        map.put("2", "two");
        map.put("가", "ㄱ");
        map.put("나", "ㄴ");
        map.put("A", "a");
        map.put("B", "b");

        System.out.println("map = " + map);
//        map = {1=one, 2=two, A=a, B=b, a=A, b=B, 가=ㄱ, 나=ㄴ}
        Set<Map.Entry<String, String>> entries = map.entrySet();
        System.out.println("entries = " + entries);
//        map = {1=one, 2=two, A=a, B=b, a=A, b=B, 가=ㄱ, 나=ㄴ}
        for(Map.Entry<String, String> tempEntry: entries){
            System.out.println(tempEntry.getKey() + " = " + tempEntry.getValue());
        }
//        1 = one
//        2 = two
//        A = a
//        B = b
//        a = A
//        b = B
//        가 = ㄱ
//        나 = ㄴ

        System.out.println("map.firstKey() = " + map.firstKey());
//        map.firstKey() = 1

        System.out.println("map.lastKey() = " + map.lastKey());
//        map.lastKey() = 나

        System.out.println("map.higherKey(\"A\") = " + map.higherKey("A"));
//        map.higherKey("A") = B

        System.out.println("map.lowerKey(\"A\") = " + map.lowerKey("A"));
//        map.lowerKey("A") = 2

    }
}

map에 데이터를 무작위로 넣었지만 key값을 기준으로

정렬되서 출력되는 걸 볼 수 있습니다.

참고로 TreeMap은 순서가 중요한 클래스이므로, 순서와 관련된 메소드들이 있습니다.

저는 firstKey(), lastKey(), higherKey(), lowerKey()를 예제로 작성해봤습니다.