Contents
Collection - Map 정리
   Dec 14, 2022     11 min read

Map

Untitled

Map이란 ?

Map 인터페이스를 구현한 Map 컬렉션 클래스들은 키와 값을 하나의 쌍으로 저장하는 방식(key-value 방식)을 사용한다.

  1. 모든 데이터는 키(key)와 값(value)이 존재한다.
  2. 키가 없이 값만 저장될 수는 없다.
  3. 값 없이 키만 저장할 수도 없다.
  4. 키는 해당 Map에서 고유해야만 한다.
  5. 값은 Map에서 중복되어도 상관 없다.
  6. 요소의 저장 순서를 유지하지 않는다.

Map은 java.util 패키지의 Map이라는 이름의 인터페이스로 선언되어 있고, 구현해 놓은 클래스들도 많이 있다.

  • Map인터페이스에 선언되어 있는 메소드들의 주요 기능
리턴 타입메소드 이름 및 매개 변수설명
Vput(K key, V value)첫 번째 변수인 키를 갖는, 두 번째 매개 변수인 값을 갖는 데이터를 저장한다.
voidputAll(Map<? extends K, ? extends V> m)매개 변수로 넘어온 Map의 모든 데이터를 저장한다.
Vget(Object key)매개 변수로 넘어온 키에 해당하는 값을 넘겨 준다.
Vremove(Object key)매개 변수로 넘어온 키에 해당하는 값을 넘겨주며, 해당 키와 값은 Map에서 삭제한다.
SetkeySet()키의 목록을 Set 타입으로 리턴한다.
Collectionvalues()값의 목록을 Collection 타입으로 리턴한다.
Set<Map.Entry<K,V»entrySet()Map 안에 Entry라는 타입의 Set을 리턴한다.
intsize()Map의 크기를 리턴한다.
voidclear()Map의 내용을 지운다.

Map을 구현한 주요 클래스들

Hashtable 클래스는 Map 인터페이스를 구현하기는 했지만 일반적인 Map 인터페이스를 구현한 클래스들과 다르다.

  • Hashtable과 Map인터페이스 구현 클래스의 다른 점
    • Map은 컬렉션 뷰(Collection View)를 사용하지만, Hashtable은 Enumeration 객체를 통해서 데이터를 처리한다.
    • Map은 키, 값, 키-값 쌍으로 데이터를 순회하여 처리할 수 있지만, Hashtable은 이 중에서 키-값 쌍으로 데이터를 순환하여 처리할 수 없다.
    • Map은 이터레이션을 처리하는 도중에 데이터를 삭제하는 안전한 방법을 제공하지만, Hashtable은 그러한 기능을 제공하지 않는다.
기능HashMapHashtable
키나 값에 null 저장 가능 여부가능불가능
여러 쓰레드 안전 여부불가능가능

HashMap 클래스에 관하여

우선 HashMap의 상속관계

java.lang.Object
    java.util.AbstractMap<K,V>
        java.util.HashMap<K,V>

어떤 인터페이스를 구현했는지 ?

인터페이스용도
Serializable원격으로 객체를 전송하거나, 파일에 저장할 수 있음을 지정
CloneableObject 클래스의 clone() 메소드가 제대로 수행될 수 있음을 지정, 즉 복제가 가능한 객체임을 의미한다.
Map맵의 기본 메소드 지정

HashMap 생성자의 구성

생성자설명
HashMap()16개의 저장 공간을 갖는 HashMap 객체를 생성한다.
HashMap(int initialCapacity)매개 변수만큼의 저장 공간을 갖는 HashMap 객체를 생성한다.
HashMap(int initialCapacity, float loadFactor)첫 매개 변수의 저장 공간을 갖고, 두 번째 매개 변수의 로드 팩터를 갖는 HashMap 객체를 생성한다.
HashMap(<Map<? extends K, ? extends V> m)매개 변수로 넘어온 Map을 구현한 객체에 있는 데이터를 갖는 HashMap 객체를 생성한다.

HashMap 객체를 생성할 때에는 매개 변수가 없는 생성자를 사용한다.

하지만 HashMap에 담을 데이터의 개수가 많은 경우에는 초기 크기를 지정해 주는 것을 권장한다.


HashMap 객체에 값을 넣고 확인

  • 예제를 통해서 HashMap 객체에 값을 넣는 것을 알아보자.
import java.util.HashMap;

public class MapSample {
    public static void main(String[] args) {
        MapSample sample = new MapSample();
        sample.checkHashMap();

    }

    public void checkHashMap() {
        HashMap<String, String> map = new HashMap<>();
        map.put("A", "a"); // HashMap에 "A"라는 키에 "a"는 값을 넣는다.
        System.out.println(map.get("A"));
        System.out.println(map.get("B"));


    }
}
  • 결과
a
null

HashMap 객체에 put() 메소드를 사용하여 이미 존재하는 키로 값을 넣으면 어떻게 될까?

map.put("A", "1");
System.out.println(map.get("A"));
  • 결과
1

이미 존재하는 키로 값을 넣으면 기존의 값을 새로운 값으로 변경된다.

반면에 ArrayList 클래스는 추가할 때 add() 메소드를 사용하면되고 수정할 때에는 set() 메소드를 사용한다.

따라서 HashMap은 ArrayList랑 수정하는 방법이 다르다.


HashMap 객체의 값을 확인하는 다른 방법

HashMap에 어떤 키가 있는지 확인하려면 어떻게 해야할까?

KeySet() 이라는 메소드를 사용하면 된다.

값을 조회하려면 get(key)을 쓴다. 하지만 값을 조회하기 앞서 키를 알아야한다. 그렇기 때문에 keySet() 을 사용해야 한다.

예제를 통해서 알아보자.

public void checkKeySet(){
    HashMap<String, String> map = new HashMap<>();
    map.put("C", "c");
    map.put("D", "d"); //①
    map.put("A", "a"); //
    Set<String> keySet = map.keySet();//②
    for (String tempKey : keySet) { //③
        System.out.println(tempKey + "=" + map.get(tempKey)); //④
    }

}

1️⃣ 데이터를 추가함

2️⃣ keySet이라는 Set 타입의 변수를 선언하고, map의 keySet() 메소드 수행 결과를 할당했다.

3️⃣ for루프를 이용해 결과를 출력

4️⃣ tempkey라는 변수에 할당되는 값들은 HashMap에 담겨 있는 키 값이 될 것이므로, get() 메소드를 사용하여 값을 확인한다.

  • 결과
A=a
C=c
D=d

결과를 보면 넣은 순서와 다르게 출력한다. 왜냐하면 List와 Queue는 순서를 중요시 여기지만 Set과 Map은 데이터를 추가 순서는 중요하지 않기 때문이다.

더 정확히 말하면 Set은 데이터가 중복되지 않는 것이 중요하고, Map은 키가 중복되지 않는 것이 중요하다.

따라서 데이터를 저장한 순서대로 결과가 출력되지는 않는다.

HashMap 객체에 담겨 있는 값만 필요할 경우에는 KeySet()메소드 사용하여 키 목록을 얻어내고, 하나 하나 받아올 필요는 없다.

values()라는 메소드가 있기 때문이다.

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

    Collection<String> values = map.values();
    for (String tempValue : values) {
        System.out.println(tempValue);
    }
}
  • 결과
a
c
d

values() 라는 메소드를 사용하면 HashMap에 담겨있는 값의 목록을 Collection 타입의 목록으로 리턴해준다.

import java.util.Collection;

선언해줘야한다.

values() 메소드를 사용하는 것이 keySet() 메소드로 모든 키 값을 가져 온 후 처리하는 것보다 더 간편하다.

위의 두 가지 말고도 데이터를 꺼내는 방법 외에 entrySet() 이라는 메소드를 사용할 수도 있다.

entrySet() 메소드를 사용하면 Map에 선언된 Entry라는 타입의 객체를 리턴한다.

Entry에는 단 하나의 키와 값만 저장된다. 따라서 getKey()getValue() 라는 메소드를 사용하면 키와 값을 간단하게 가져올 수 있다.

public void checkHashMapEntry() {
    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();
        for (Map.Entry<String, String> tempEntry : entries) {
            System.out.println(tempEntry.getKey() + "=" + tempEntry.getValue());
     }
}

map의 entrySet()이라는 메소드를 호출하면 Set 타입으로 리턴하며, 그 Set 내에는 Entry 타입으로 데이터가 저장된다.

entrySet() 메소드가 제대로 수행되도록 하려면 클래스 선언문 위에 두 개의 인터페이스를 import해야만 한다.

import java.util.Map.Entry;
import java.util.Set;
  • 결과
A=a
B=b
C=c
D=d

Map에 key, value의 존재를 확인하는 메소드

containskey() , containsValue()

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

    System.out.println(map.containsKey("A"));
    System.out.println(map.containsKey("Z"));
    System.out.println(map.containsValue("a"));
    System.out.println(map.containsValue("z"));
}
  • 결과
true
false
true
false

무작정 get() 메소드로 해당 키나 값이 존재하는지 확인하는 것보다는 이렇게 containsKey()containsValue() 메소드를 사용하는 것이 효과적이다.


Map 데이터 삭제 메소드

public void checkRemove(){
    HashMap<String, String> map = new HashMap<>();
    map.put("A", "a");
    System.out.println(map.size());

    map.remove("A");
    System.out.println(map.size());
}
  • 결과
1
0

“A” 를 키로 갖는 데이터를 삭제하려면, remove() 메소드의 매개 변수로 전달해주면 된다.


정렬된 키의 목록은 TreeMap

HashMap 객체의 키를 정렬 하려면 Arrays라는 클래스를 사용하는 방법이 있다.

하지만 불필요한 객체가 생긴다는 단점이 있다.

이러한 단점을 보완하는 클래스 → TreeMap 클래스

  • key의 정렬의 기본적인 순서 (String 문자열이 저장될 때 순서)
    • 숫자 > 알파벳 대문자 > 알파벳 소문자 > 한글
  • 예제
public class TreeMapSample {
    public static void main(String[] args) {
        TreeMapSample sample = new TreeMapSample();
        sample.checkTreeMap();

    }

    public void checkTreeMap() {

        TreeMap<String, String> map = new TreeMap<>();
        map.put("A", "a");
        map.put("가", "e");
        map.put("1", "f");
        map.put("a", "g");
        Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, String> tempEntry : entries) {
            System.out.println(tempEntry.getKey() + "=" + tempEntry.getValue());
        }

        System.out.println();
        System.out.println(map.firstEntry());
        System.out.println();
        System.out.println(map.firstKey());

    }
}
  • 결과
1=f
A=a
a=g
=e

TreeMap 은 키를 정렬하여 저장하고, 키의 목록을 가져와서 출력하면 정렬된 순서로 볼 수 있다.

매우 많은 데이터를 TreeMap을 이용하여 보관하여 처리할 때에는 HashMap보다는 느릴 것이다.

이유는 키가 정렬되기 때문이다.

하지만 100건, 1000건 정도의 데이터를 처리하고, 정렬을 해야 할 필요가 있다면, HashMap 보다는 TreeMap을 사용하는 것이 더 좋다.

TreeMap이 키를 정렬하는 것은 SortedMap이라는 인터페이스를 구현했기 때문이다.

SortedMap 을 구현한 클래스들은 모두 키가 정렬되어 있어야만 한다.

키가 정렬되었을 떄 장점은 가장 앞에 있는 키(firstKey()), 가장 뒤에 있는 키(lastKey()), 특정 키 뒤에 있는 키(higherKey()), 특정 키 앞에 있는 키(lowerKey()) 등을 알 수 있는 메소드를 제공해준다는 점이다.


Map을 구현한 Properties 클래스

Properties 라는 클래스는 HashTable을 확장(extends)하였다. 따라서 Map 인터페이스에서 제공하는 모든 메소드를 사용할 수 있다.

Properties 클래스는 시스템의 속성을 제공해준다.

public class PropertiesSample {
    public static void main(String[] args) {
        PropertiesSample sample = new PropertiesSample();
        sample.checkProperties();
    }

    public void checkProperties(){
        Properties prop = System.getProperties();
        Set<Object> keySet = prop.keySet();
        for (Object tempObject : keySet) {
            System.out.println(tempObject + "=" + prop.get(tempObject));
        }
    }
}
  • 결과
os.name=Mac OS X
java.vm.specification.version=11
sun.java.launcher=SUN_STANDARD
user.country=KR
//...

여러 시스템 속성이 나온다.

  • 시스템 속성 중 기억하면 좋은 것들을 알아보자.
  • 간단하고 빠르게 시스템의 속성을 확인하려면 properties 메소드를 사용하면 확인할 수 있다.
  • HashTable을 확장한 클래스이기 때문에 키와 값의 형태로 데이터가 저장되어 있다.
속성설명
user.language사용자의 사용 언어
user.dir현재 사용중인 기본 디렉토리
user.name사용자 계정의 홈 디렉터리
java.io.tmpdir자바에서 사용하는 임시 디렉터리
file.encoding파일의 기본 인코딩
sun.io.unicode.encoding유니코드 인코딩
path.separator경로 구분자
file.separator파일 구분자
line.separator줄(line) 구분자
  • Properties 클래스를 사용하는 이유
    • HashTable, HashMap에 있는 속성을 사용하면 되긴하다만 Properties 클래스에서 추가로 제공하는 메소드들을 보면 알수 있음.
  • Properties 클래스에서 추가로 제공하는 메소드들
리턴 타입메소드 이름 및 매개 변수설명
voidload(InputStream inStream)파일에서 속성을 읽는다.
voidload(Reader reader)파일에서 속성을 읽는다.
voidloadFromXML(InputStream in)XML로 되어 있는 속성을 읽는다.
voidstore(OutputStream out, String comments)파일에 속성을 저장한다.
voidstore(Writer writer, String comments)파일에 속성을 저장한다.
voidstoreToXML(OutputStream os, String comment)XML로 구성되는 속성 파일을 생성한다.
voidstoreToXML(OutputStream os, String comment, String encoding)XML로 구성되는 속성 파일을 생성한다.

이상으로 Map에 대한 이야기는 끝났다. Properties 예제를 통해서 알면 좋겠지만 뒤에 나오는 IO 때 정리하는 것이 더 좋은 생각이 들어서 여기서는 메소드만 알아가자.


참고 자료

  • 자바의 신