본문 바로가기
Java

[Java] Collection Framework 3 - Map(HashMap / LinkedHashMap / TreeMap / HashTable/ Properties)

by 건복치 2020. 5. 20.
반응형

Map 컬렉션

  • 키(key)와 값(value)으로 구성된 Entry 객체를 저장하는 구조
  • 키와 값은 모두 객체
  • 키는 중복 저장될 수 없지만 값은 중복 저장될 수 있다.
  • 만약 기존에 저장된 키와 동일한 키로 값을 저장하면 기존의 값은 없어지고 새로운 값으로 대치된다.
  • Map 컬렉션에는 HashMap, Hashtable, LinkedHashMap, Properties, TreeMap 등이 있다.

 

 

Map 인터페이스 메소드

메소드의 매개 변수 타입과 리턴 타입에 K, V 타입 파라미터가 지정되어 있다.

왜냐하면 Map 인터페이스가 제네릭 타입이기 때문에 구체적인 타입은 구현 객체를 생성할 때 결정된다. 따라서 K, V로 주어진 것.

 

기능 메소드 설명
객체 추가 V put(K key, V value) 주어진 객체를 저장
객체가 성공적으로 저장되면 true 리턴
중복 객체면 false 리턴
객체 검색 boolean containsKey(Object key) 주어진 객체가 저장되어 있는지 여부 반환
- boolean containsAll(Collection<?> c)
boolean containsValue(Object value) 컬렉션이 비어 있는지 조사
Set(Map.Entry() 저장된 객체를 한 번씩 가져오는 반복자 리턴
int size() 저장되어 있는 전체 객체 수 리턴
Object toArray() 저장된 객체들을 객체 배열의 형태로 반환
객체 삭제 void clear() 저장된 모든 객체를 삭제
boolean remove(Object o) 주어진 객체를 삭제
- boolean removeAll(Collection<?> c)

 

* getOrDefault(key, defaultValue) : 찾는 키가 존재한다면 키의 값을 반환하고 없다면 default + 값 반환

 

Map<String, Integer> map = new Map<>();
map.put("KMH", 26); //객체 추가
int age = map.get("KMH"); //객체 찾기
map.remove("KMH"); //객체 삭제

 

Key와 Value값 찾기

키를 알고 있다면 get() 메소드로 간단하게 객체를 찾아오면 되지만,

저장된 객체를 대상으로 하나씩 얻고 싶을 경우에는 두 가지 방법을 사용할 수 있다.

 

1. keySet() 메소드 이용

keySet() 메소드로 모든 키를 Set 컬렉션으로 얻은 다음,

반복자를 통해 키를 하나씩 얻고 get() 메소드를 통해 값을 얻으면 된다.

 

Map<K, V> map = ...;
Set<K> keySet = map.keySet();
Iterator<K> keyIterator = keySet.iterator();
while(keyIterator.hasNext()) {
	K key = keyIterator.next();
    V value = map.get(key);
}

 

2. entrySet() 메소드 이용

entrySet() 메소드로 모든 Map.Entry를 Set 컬렉션으로 얻은 다음,

반복자를 통해 Map.Entry를 하나씩 얻고 getKey()와 getValue() 메소드를 이용해 키와 값을 얻으면 된다.

 

Set<Map.Entry<K, V>> entrySet = map.entrySet();
Iterator<Map.Entry<K,V>> entryIterator = entrySet.iterator();
while(entryIterator.hasNext()) {
	Map.Entry<K, V> entry = entryIterator.next();
    K key = entry.getKey();
    V value = entry.getValue();
}

 

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

public class Map_Print {
    public static void main(String[] agrs) {
        HashMap<String, String> map = new HashMap<String, String>(); 
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");

        //방법 1-1 : keySet() & Iterator
        System.out.println("방법 1-1 : keySet() & Iterator");
        Iterator<String> keys = map.keySet().iterator();
        while(keys.hasNext() ){
            String key = keys.next();
            String value = map.get(key);
            System.out.println("키 : " + key + ", 값 : " + value);
        }
        
        //방법 1-2 : keySet() & for 
        System.out.println("\n방법 1-2 : keySet() & for");
        for(String key : map.keySet() ){
            String value = map.get(key);
            System.out.println("키 : " + key + ", 값 : " + value);
        }
        
        //방법 2 : entrySet() 
        System.out.println("\n방법 2 : entrySet()");
        for(Map.Entry<String, String> element : map.entrySet() ){
            String key = element.getKey();
            String value = element.getValue();
            System.out.println("키 : " + key + ", 값 : " + value);
        }
    }
}

 

 

지금까지는 key값으로 key와 value값을 찾았는데 value값으로 key를 찾고 싶다면?

3. value값으로 key값 찾기

1번, 2번 방법 둘 다 keySet()메소드로 map의 키를 확인하면서 매개변수로 주어진 value를 가진 key를 발견하면 리턴하는 방법이다.

1번처럼 K, V 타입 파라미터로 리턴 값과 매개변수 값을 주어도 되고, 2번처럼 Object 타입을 리턴값과 매개변수 값으로 주어도 된다.

 

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

public class Map_Print {
	public static void main(String[] agrs) {
		HashMap<String, String> map = new HashMap<String, String>(); 
		map.put("key1", "value1");
		map.put("key2", "value2");
		map.put("key3", "value3");

		//value값으로 key 찾기
		System.out.println("\nvalue값으로 key 찾기1 : " + getKey(map, "value1"));
		System.out.println("\nvalue값으로 key 찾기2 : " + getKey2(map, "value1"));

	}

	//value로 key 찾기 1
	public static <K, V> K getKey(Map<K, V> map, V value) {
		for (K key : map.keySet()) {
			if (value.equals(map.get(key))) {
				return key;
			}
		}
		return null;
	}

	//value로 key 찾기 2
	public static Object getKey2(HashMap<String, String> map, Object value) {
		for(Object o: map.keySet()) {
			if(map.get(o).equals(value)) { 
				return o; 
			}
		} return null;
	}
	
}

 

HashMap

HashMap은 Map 인터페이스를 구현한 대표적인 Map 컬렉션이다.

 

HashMap 내부에서 HashMap의 키로 사용할 객체는 hashCode()와 equals() 메소드를 재정의해서 동등 객체가 될 조건을 정한다.

동등 객체, 즉 동일한 키가 될 조건은 hashCode()의 리턴 값이 같아야 하고, equlas() 메소드가 true를 리턴해야 한다.

주로 키 타입은 String을 많이 사용하는데 String은 문자열이 같을 경우 동등 객체가 될 수 있도록 hashCode()와 equals() 메소드가 재정의되어 있다.

 

 

 

HashMap을 생성하기 위해서는 키 타입과 값 타입을 파라미터로 주고 기본 생성자를 호출하면 된다.

 

Map<K, V> map = new HashMap<K, V>();

 

키와 값의 타입은 기본 타입(byte, shortm int, float, double, boolean, char)을 사용할 수 없고 클래스 및 인터페이스 타입만 가능하다.

 

키로 String 타입을 사용하고 값으로 Integer 타입을 사용하는 HashMap은 다음과 같이 생성할 수 있다.

 

Map<String, Integer> map = new HashMap<String, Integer>();

 

Hashtable

Hashtable은 HashMap과 동일한 내부 구조를 가지고 있다.

Hashtable도 키로 사용할 객체를 hashCode()와 equlas() 메소드를 재정의해서 동등 객체가 될 조건을 정해야 한다.

HashMap과의 차이점은 Hashtable은 동기화된(Synchronized) 메소드로 구성되어 있기 때문에 멀티 스레드가 동시에 이 메소드들을 실행할 수는 없고, 하나의 스레드가 실행을 완료해야만 다른 스레드를 실행할 수 있다.

그래서 멀티 스레드 환경에서 안전하게 객체를 추가, 삭제할 수 있다. 이것을 스레드가 안전(thread safe)하다고 말한다.

 

 

 

Hashtable의 생성 방법은 HashMap과 크게 다르지 않다.

키 타입과 값 타입을 지정하고 기본 생성자를 호출하면 된다.

 

Map<String, Integer> map = new Hashtable<String, Integer>();

 

Properties

Properties는 Hashtable의 하위 클래스이기 때문에 Hashtable의 모든 특징을 그대로 가지고 있다.

차이점은 Hashtable은 키와 값을 다양한 타입으로 지정이 가능한데 비해 Properties는 키와 값을 String 타입으로 제한한 컬렉션이다.

Properties는 애플리케이션의 옵션 정보, 데이터베이스 연결 정보 그리고 국제화(다국어) 정보가 저장된 프로퍼티(~. Properties) 파일을 읽을 때 주로 사용한다.

 

자세히 보려면 더보기 클릭(아직 정리 X)

더보기

프로퍼티 파일은 키와 값이 = 기호로 연결되어 있는 텍스트 파일로 ISO 8859_1 문자셋으로 저장된다.

이 문자셋으로 직접 표현할 수 없는 한글은 유니코드(Unicode)로 변환되어 저장된다.
예를 들어 country와 language 키로 각각 "대한민국", "한글"을 입력하면 자동으로 유니코드로 변환되어 저장된다.

이클립스에서 유니코드로 변환된 내용을 다시 한글로 보려면 마우스를 유니코드 위에 올려놓으면 된다.

 

country=대한민국
language=한글

 

country=
language=

 

이클립스를 사용하지 않는다면, 한글이 포함된 프로퍼티 파일을 다른 에디터에서 작성하고 <JDK 설치폴더>\bin\native2ascii.exe 툴을 이용해서 ISO 8859_1 파일을 얻으면 된다.

 

native2ascii.exe

 

프로퍼티 파일을 읽기 위해서는 Properties 객체를 생성하고, load() 메소드를 호출하면 된다.

load() 메소드는 프로퍼티 파일로부터 데이터를 읽기 위해 FileReader 객체를 매개 값으로 받는다.

 

//코드

 

프로퍼티 파일은 일반적으로 클래스 파일(~.class)과 함께 저장된다.

클래스 파일을 기준으로 상대 경로를 이용해서 프로퍼티 파일의 경로를 얻으려면 Class의 getResource() 메소드를 이용하면 된다.

getResuore()는 주어진 파일의 상대 경로를 URL 객체로 리턴하는데, URL의 getPath()는 파일의 절대 경로를 리턴한다.

 

다음은 클래스 파일과 동일한 위치에 있는"database properties"파일을 읽고 Properties 객체를 생성하는 코드이다.

 

//코드

 

만약 다른 패키지에 프로퍼티 파일이 있을 경우에는 경로 구분자로 "\"를 사용한다.

예를 들어 A.class가 com.mycompany 패키지에 있고, database.properties 파일이 com.mycompant.config 패키지에 있을 경우 프로퍼티 파일의 절대 경로를 다음과 같이 얻을 수 있다.

 

//코드

 

Properties 객체에서 해당 키의 값을 읽으려면 getProperty() 메소드를 사용한다.

물론 Properties도 Map 컬렉션이므로 get() 메소드로 값을 얻을 수 있다.

그러나 get() 메소드는 값을 Object 타입으로 리턴하므로 강제 타입 변환해서 String을 얻어야 하기 때문에 일반적으로 getProperty() 메소드를 사용한다.

 

String value = properties.getProperty("key");

 

 

TreeMap

TreeMap은 이진트리를 기반으로 한 Map 컬렉션이다.

TreeSet과의 차이점은 키와 값이 저장된 Map.Entry를 저장한다는 점이다.

TreeMap에 객체를 저장하면 자동으로 정렬되는데, 기본적으로 부모 키값과 비교해서 키 값이 낮은 것은 왼쪽 자식 노드에, 키 값이 높은 것은 오른쪽 자식 노드에 Map.Entry 객체를 저장한다.

 

 

 

TreeMap을 생성하기 위해서는 키로 저장할 객체 타입과 값으로 저장할 객체 타입을 타입 파라미터로 주고 기본 생성자를 호출하면 된다.

TreeMap<K, V> treeMap = new TreeMap<K, V>();

TreeMap<String, Integer> treeMap = new TreeMap<String, Integer>();

 

TreeMap의 검색 관련 메소드

리턴 타입 메소드 설명
Map.Entry(K, V) firstEntry() 제일 낮은 Map.Entry를 리턴
Map.Entry(K, V) lastEntry() 제일 높은 Map.Entry를 리턴
Map.Entry(K, V) lowerEntry(K key) 주어진 키보다 바로 아래 Map.Entry를 리턴
Map.Entry(K, V) higherEntry(K key) 주어진 키보다 바로 위 Map.Entry를 리턴
Map.Entry(K, V) floorEntry(K key) 주어진 키와 동등한 키가 있으면 해당 Map.Entry를 리턴, 없다면 주어진 키 바로 아래의 Map.Entry를 리턴
Map.Entry(K, V) ceilingEntry(K key) 주어진 키와 동등한 키가 있으면 해당 Map.Entry를 리턴, 없다면 주어진 키 바로 위의 Map.Entry를 리턴
Map.Entry(K, V) pollFirstEntry() 제일 낮은 Map.Entry를 꺼내오고 컬렉션에서 제거함
Map.Entry(K, V) pollLastEntry() 제일 높은 Map.Entry를 꺼내오고 컬렉션에서 제거함

 

TreeMap의 정렬 관련 메소드

리턴 타입 메소드 설명
NavigableSet<K> descendingKeySet() 내림차순으로 정렬된 키의 NavigableSet을 리턴
NavigableMap<K, V> descendingMap() 내림차순으로 정렬된 Map.Entry의 NavigableMap을 리턴

 

  • descendingKeySet() 메소드는 Collection Framework2의 TreeSet 부분 참고
  • descendingMap() 메소드는 내림차순으로 정렬된 NavigableMap 객체를 리턴하며 HashMap과 마찬가지로 검색 관련 메소드를 제공
  • 오름차순으로 정렬하고 싶다면 descendingMap() 메소드를 두 번 호출하면 된다.

 

TreeMap의 범위 검색 관련 메소드

더보기
리턴 타입 메소드 설명
NavigableMap<K, V> headMap(
    K toKey,
    boolean inclusive
)
- 키 / Map.Entry포함여부

- 주어진 키보다 낮은 Map.Entry들을 NavigableMap으로 리턴.
주어진 Map.Entry 포함 여부는 2번째 매개값에 따라 달라짐
NavigableMap<K, V> tailMap(
    K fromKey,
    boolean inclusive
)
- 키 / 포함여부

- 주어진 Map.Entry보다 높은 객체들을 
NavigableMap으로 리턴.
주어진 Map.Entry 포함 여부는 2번째 매개값에 따라 달라짐
NavigableMap<K, V> subMap(
    K fromKey
    boolean fromInclusive,
    K toKey,
    boolean toInclusive
)
- 시작 키 / 시작 Map.Entry 포함여부 / 끝 키 / 끝 Map.Entry 포함여부

- 시작과 끝으로 주어진 키 사이의 Map.Entry들을 NavigableMap으로 리턴.
시작과 끝 키의 Map.Entry포함 여부는 2번째, 4번째 매개값에 따라 달라짐

 

ex) subMap()

시작 Map.Entry < 찾는 Map.Entry < 끝 Map.Entry

시작 Map.Entry <= 찾는 Map.Entry <= 끝 Map.Entry

 

 

 

관련 포스트

[Java] Collection Framework1 - List(ArrayList / Vector / LinkedList)
[Java] Collection Framework2 - Set(HashSet / LinkedHashSet / TreeSet)

 

참고

이것이 자바다 - 신용권의 Java 프로그래밍 정복 2

 

반응형

댓글