본문으로 바로가기

1. Map 인터페이스

  • 키(Key)와 값(Value)으로 구성된 Entry 객체를 저장하는 구조
  • 키와 값은 모두 객체입니다.
  • 키는 중복 저장될 수 없지만 값은 중복 저장될 수 있습니다.
  • 이미 저장된 키와 동일한 키로 값을 저장하게 되면 기존의 값은 없어지고 새로운 값으로 변경됩니다.
  • 구현 클래스로는 HashMap, Hashtable, LinkedHashMap, Properties, TreeMap이 있습니다.

 

2. Map 인터페이스 주요 기능

[객체 추가]

V put(K key, V value) → 주어진 키와 값을 추가합니다.

 

[객체 검색]

boolean containsKey(Object key) → 주어진 키가 존재하는지 여부

boolean containsValue(Object value) → 주어진 값이 존재하는지 여부

Set<Map.Entry<K,V>> entrySet() → 키와 값의 쌍으로 구성된 모든 Map.Entry 객체를 Set에 담아서 리턴

V get(Object key) → 주어진 키가 있는 값을 리턴

boolean isEmpty() → 컬렉션이 비어 있는지 여부

Set<K> keySet() → 모든 키를 Set 객체에 담아서 리턴

int size() → 저장된 키의 총 수를 리턴

Collection<V> values() → 저장된 모든 값을 Collection에 담아서 리턴

 

[객체 삭제]

void clear() → 모든 Map.Entry를 삭제

V remove(Object key) → 주어진 키와 일치하는 Map.Entry를 삭제

 

3. HashMap

  • 키의 순서가 보장되지 않습니다.
  • HashMap의 키로 사용할 객체는 hashCode()와 equals() 메소드를 오버로딩해서 객체를 비교(동등 조건 비교)할 조건을 정해야합니다.

객체 비교 순서도

예시 (String key, String Value)

// HashMap 생성
Map<String, String> hashMap = new HashMap();

// 객체 추가
hashMap.put("lee", "Incheon");
hashMap.put("jang", "Seoul");
hashMap.put("kim", "Jeju");

// 객체 검색
System.out.println(hashMap.get("lee"));

// 루핑 (foreach)
for(String k : hashMap.keySet()) {
    System.out.println(k + " : " + hashMap.get(k));
}

// 루핑 (lambda)
hashMap.forEach((k,v) -> {
    System.out.println(k + " : " + k);
});

// 객체 삭제
hashMap.remove("kim");

// 주어진 키가 존재하는지 확인
System.out.println(hashMap.containsKey("kim"));

 

4. LinkedHashMap

HashMap과 거의 동일하나 LinkedHashMap은 Key의 순서를 보장합니다.

 

예시(HashMap vs LinkedHashMap)

Map<Integer, String> hashMap = new HashMap();
hashMap.put(3,"jang");
hashMap.put(2,"lee");
hashMap.put(4,"kim");
hashMap.put(1,"kim");

hashMap.forEach((k,v) -> {
    System.out.println(k + " : " + v);
});

/**
 * result
 * 1 : kim
 * 2 : lee
 * 3 : jang
 * 4 : kim
 */

System.out.println("------");

Map<Integer, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put(3,"jang");
linkedHashMap.put(2,"lee");
linkedHashMap.put(4,"kim");
linkedHashMap.put(1,"kim");

linkedHashMap.forEach((k,v) -> {
    System.out.println(k + " : " + v);
});

/**
 * result
 * 3 : jang
 * 2 : lee
 * 4 : kim
 * 1 : kim
 */

 

5. Hashtable

Thread Safe

  • Hashtable 또한 HashMap과 동일한 내부 구조를 가지고 있습니다. (HashCode(), equals())
  • HashMap과의 차이점은 Hashtable은 동기화된(synchronized) 메소드로 구성되어 있어 멀티 스레드 환경에서 안전합니다. (Thread Safe)

 

6. Properties

  • Hashtable의 하위 클래스이므로 Hashtable의 모든 특징을 가지고 있습니다.
  • Properties는 키와 값을 String으로 제한합니다.
  • ~.properties 파일을 읽을 때 주로 사용합니다.
  • 스프링 부트 프레임워크에서 더 편리한 방법들을 제공하므로 생략합니다.

 

7. TreeMap

  • 이진트리를 기반으로 한 Map 컬렉션입니다.
  • 키와 값이 저장된 Map.Entry를 저장합니다.
  • TreeMap에 객체를 저장하면 자동으로 정렬됩니다. (오름차순)
  • 부모 노드의 키값과 비교하여 키 값이 작은 것은 왼쪽 자식 노드, 키 값이 큰 것은 오른쪽 자식 노드에 객체를 저장합니다.

[이진트리]

- 여러 개의 노드가 트리 형태로 연결된 구조

- 루트 노드에서부터 시작해 각 노드 최대 2개의 노드를 연결할 수 있는 구조

- 위아래로 연결된 두 노드 = 부모-자식 관계 (부모 노드, 자식 노드)

- 하나의 부모 노드는 최대 두 개의 자식 노드와 연결될 수 있습니다.

- 부모 노드의 값 보다 작은 노드는 왼쪽

- 부모 노드의 값 보다 큰 노드는 오른쪽

- 왼쪽 마지막 노드가 제일 작은 값

- 오른쪽 마지막 노드가 가장 큰 값

- 오름차순(왼쪽 마지막 노드에서부터 오른쪽 마지막 노드까지 [왼쪽]→[부모][오른쪽] 순으로 읽음)

- 내림차순(오른쪽 마지막 노드에서부터 왼쪽 마지막 노드까지 [오른쪽][부모][왼쪽] 순으로 읽음)

- 그룹핑에 매우 유리합니다.

 

TreeMap 기능

TreeMap 같은 경우엔 Map 인터페이스 타입에 대입해서 사용해도 되지만 TreeMap 타입으로 대입해야 TreeMap이 가지고 있는 검색 관련 기능들을 사용할 수 있습니다.

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

[검색 관련 기능] 

Map.Entry<K,V> firstEntry() → 제일 작은 객체를 반환

Map.Entry<K,V> lastEntry() → 제일 큰 객체를 반환

Map.Entry<K,V> lowerEntry(K key) → 주어진 객체보다 작은 데이터 중 최댓값 반환(주어진 객체보다 바로 아래 객체를 반환)

Map.Entry<K,V> higherEntry(K key) → 주어진 객체보다 큰 데이터 중 최솟값 반환(주어진 객체보다 바로 위 객체를 반환)

Map.Entry<K,V> floorEntry(K key) → 주어진 객체와 같은 값이 있으면 반환, 없다면 주어진 객체보다 작은 데이터 중 최댓값 반환 

Map.Entry<K,V> ceilingEntry(K key)  주어진 객체와 같은 값이 있으면 반환, 없다면 주어진 객체보다 큰 데이터 중 최솟값 반환

Map.Entry<K,V> pollFirstEntry() → 제일 작은 객체를 반환하고 컬렉션에서 제거

Map.Entry<K,V> pollLastEntry() → 제일 큰 객체를 반환하고 컬렉션에서 제거

// 키(cm), 이름을 저장
TreeMap<Integer, String> treeMap = new TreeMap<>();

treeMap.put(172,"kim");
treeMap.put(160,"lee");
treeMap.put(180,"jang");
treeMap.put(175,"shin");
treeMap.put(185,"choo");

System.out.println(treeMap.firstEntry()); // 160=lee
System.out.println(treeMap.lastEntry()); // 185=choo

System.out.println(treeMap.lowerEntry(180)); // 175=shin
System.out.println(treeMap.higherEntry(180)); // 185=choo

System.out.println(treeMap.floorEntry(165)); // 160=lee
System.out.println(treeMap.ceilingEntry(165)); // 172=kim

System.out.println(treeMap.pollFirstEntry()); // 160=lee
System.out.println(treeMap.pollLastEntry()); // 185=choo

 

[정렬 관련 기능]

NavigableSet<K> descendingKeySet() → 내림차순으로 정렬된 키의 NavigableSet을 리턴

NavigableMap<K,V> descendingMap() → 내림차순으로 정렬된 Map.Entry의 NavigableMap을 리턴

 

※ 오름차순으로 정렬하고 싶으면 descendingMap() 메소드를 두 번 호출합니다.

// 키(cm), 이름을 저장
TreeMap<Integer, String> treeMap = new TreeMap<>();

treeMap.put(172,"kim");
treeMap.put(160,"lee");
treeMap.put(180,"jang");
treeMap.put(175,"shin");
treeMap.put(185,"choo");

// 내림차순
NavigableMap<Integer, String> navigableMapDesc = treeMap.descendingMap();
navigableMapDesc.forEach((k,v) -> {
    System.out.println(k + " : " + v);
});

// 오름차순
NavigableMap<Integer, String> navigableMapAsc = navigableMapDesc.descendingMap();
navigableMapAsc.forEach((k,v) -> {
    System.out.println(k + " : " + v);
});

[범위 검색 관련 기능]

NavigableMap<K,V> headMap(E toElement, boolean inclusive)  

→ 주어진 객체보다 작은 객체들을 NavigableMap으로 반환, 주어진 객체 포함 여부는 두 번째 매개 값으로 설정

 

NavigableMap<K,V> tailMap(E fromElement, boolean inclusive)  

→ 주어진 객체보다 큰 객체들을 NavigableMap으로 반환, 주어진 객체 포함 여부는 두 번째 매개 값으로 설정

 

NavigableMap<K,V> subMap(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive

→ 시작과 끝으로 주어진 객체 사이의 객체들을 NavigableMap으로 반환, 시작과 끝 객체의 포함 여부는 두 번째 네 번째 매개 값으로 설정 

// 키(cm), 이름을 저장
TreeMap<Integer, String> treeMap = new TreeMap<>();

treeMap.put(172,"kim");
treeMap.put(160,"lee");
treeMap.put(180,"jang");
treeMap.put(175,"shin");
treeMap.put(185,"choo");

// 180(key) 보다 작은 Entry 반환(180 포함)
NavigableMap<Integer, String> headMap1 = treeMap.headMap(180, true);
headMap1.forEach((k,v) -> {
    System.out.println(k + " : " + v);
});

System.out.println("-----------------------------------");

// 180(key) 보다 작은 Entry 반환(180 미포함)
NavigableMap<Integer, String> headMap2 = treeMap.headMap(180, false);
headMap2.forEach((k,v) -> {
    System.out.println(k + " : " + v);
});

System.out.println("-----------------------------------");

// 160 ~ 185 (160, 180 포함)
NavigableMap<Integer, String> subMap1 = treeMap.subMap(160, true, 185, true);
subMap1.forEach((k,v) -> {
    System.out.println(k + " : " + v);
});

System.out.println("-----------------------------------");

// 160 ~ 185 (160, 180 미포함)
NavigableMap<Integer, String> subMap2 = treeMap.subMap(160, false, 185, false);
subMap2.forEach((k,v) -> {
    System.out.println(k + " : " + v);
});