Dico

[Java] Class HashMap

  • 민갤

Collection Framework

데이터 군을 저장하는 클래스들을 표준화한 설계

컬렉션 Collection :  다수의 데이터. 데이터 그룹

프레임웍 Framework :  표준화된 프로그래밍 방식.

Class HashMap

Map을 구현한 컬렉션 클래스.

키와 값을 하나의 데이터(Entry)로 저장한다.

해싱을 사용하기 때문에 많은 양의 데이터를 빠르게 검색할 수 있다.

Hashtable(컬렉션 프레임웍 이전 클래스) → HashMap(컬렉션 프레임웍 이후 클래스. Hashtable 대체)

키와 값

키와 값을 모두 Object 형태로 저장하기 때문에 어떤 객체도 저장할 수 있다.

따라서 하나의 키에 다시 복수의 데이터를 저장할 수 있다. (HashMap의 값으로 HashMap을 저장)

키는 주로 String을 대문자/소문자로 통일해서 사용하곤 한다.

  • 키 key :  저장된 값을 찾는 데 사용한다. 컬렉션 내의 키 중에서 유일해야 한다.
  • 값 value :  키와 달리 데이터의 중복을 허용한다.

데이터의 무결성 Consistency

키와 값은 서로 관련된 값이기 때문에 각각의 배열로 선언하지 않고 하나의 클래스로 정의해서 하나의 배열로 다룬다.

Entry라는 내부 클래스를 정의하고, 다시 Entry 타입의 배열을 선언하고 있다.

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {

    transient HashMapEntry<K,V>[] table = (HashMapEntry<K,V>[]) EMPTY_TABLE;
    ...

    static class HashMapEntry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        ...
    }
}

  • 비객체지향적인 코드
Object[] key;
Object[] value;

  • 객체지향적인 코드
Entry[] table;
class Entry{
    Object key;
    Object value;
}

자료 구조

저장할 데이터의 키를 해시함수에 넣으면 배열의 한 요소를 얻게 되고, 다시 그 곳에 연결되어 있는 LinkedList에 저장하게 된다.

Hash TableLinkedList
key[0]HashCode[0]value
key[n-1]HashCode[n-1]value, value
key[n]HashCode[n]value

1. 검색하고자 하는 값의 키로 해시함수를 호출한다.

2. 해시함수의 계산 결과인 해시코드를 이용해서 해당 값이 저장되어 있는 LinkedList를 찾는다.

3. LinkedList에서 검색한 키와 일치하는 데이터를 찾는다.

구현

하나의 LinkedList에 최소한의 데이터만 저장하도록 구현해야 한다.

  • 저장될 데이터의 크기를 고려해서 HashMap의 크기를 적절히 지정한다.
  • 해시 함수가 서로 다른 키에 대해서 중복된 해시코드를 반환하는 것을 최소화 한다.

Public constructors

생성자설명
  HashMap(int initialCapacity, float loadFactor)  지정된 초기 용량과 load factor의 HashMap 객체 생성
  HashMap(int initialCapacity)  지정된 값을 초기 용량으로 하는 HashMap 객체 생성
  HashMap()  HashMap 객체 생성
  HashMap(Map<? extends K, ? extends V> m)  지정된 Map의 모든 요소를 포함하는 HashMap 생성

Public methods

반환 타입이름설명
  void  clear()  HashMap에 저장된 모든 객체 제거
  Object  clone()  현재 HashMap을 복제해서 반환
  boolean  containsKey(Object key)  지정된 키가 포함되어 있는 지 확인. 있으면 true
  boolean  containsValue(Object value)  지정된 값이 포함되어 있는 지 확인. 있으면 true
  Set<Entry<K, V>>  entrySet()  키와 값을 Entry(키와 값의 결합) 형태로 Set에 저장해서 반환
  void  forEach(BiConsumer<? super K, ? super V> action)  모든 Entry에 지정된 액션을 실행
  V  get(Object key)  지정된 키의 값을 반환. 못 찾으면 null
  boolean  isEmpty()  HashMap이 비어있는 지 확인
  Set  keySet()  모든 키를 Set에 저장해서 반환
  V  put(K key, V value)  지정된 키와 값을 HashMap에 저장
  void  putAll(Map<? extends K, ? extends V> m)  Map에 저장된 모든 요소를 HashMap에 저장
  V  remove(Object key)  HashMap에서 지정된 키로 저장된 값 제거
  boolean  replace(K key, V oldValue, V newValue)  지정된 키와 객체가 모두 일치하는 경우에만 새로운 객체로 대체
  void  replaceAll(BiFunction<? super K, ? super V, ? extends V> function)  모든 키의 값을 지정된 함수의 결과로 대체
  int  size()  HashMap에 저장된 요소의 개수를 반환
  Collection  values()  HashMap에 저장된 모든 값을 컬렉션의 형태로 반환

Example0

forEach()로 각 엔트리에 대해 일괄적인 액션을 수행하거나,

replaceAll()로 모든 값을 새로운 값으로 대체할 수 있다.

import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        HashMap<String, Boolean> map = new HashMap<>();
        map.put("A", false);
        map.put("B", true);
        map.put("C", true);
        map.forEach((k, v) -> System.out.println(k + " " + v));

        map.replaceAll((k, v) -> false);
        map.forEach((k, v) -> System.out.println(k + " " + v));
    }
}   
A false
B true
C true
- replaceAll 후 -
A false
B false
C false

Example1

인터페이스 Map.Entry를 이용하여 entrySet()으로 키와 값을 함께 읽어 오거나,

KeySet()/values()로 키와 값을 따로 읽어 올 수 있다.

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("A", 1);
        map.put("B", 3);
        map.put("C", 2);
        map.put("D", 4);
        map.put("E", 9);

        Set<Entry<String, Integer>> set = map.entrySet();
        Iterator<Entry<String, Integer>> it = set.iterator();

        while (it.hasNext()) {
            Map.Entry<String, Integer> e = it.next();
            System.out.println(e.getKey() + " " + e.getValue());
        }
        System.out.println("All keys: " + map.keySet());

        Collection<Integer> values = map.values(); // All Values
        Iterator<Integer> it1 = values.iterator();

        int total = 0;
        while (it1.hasNext()) {
            Integer i = (Integer) it1.next();
            total += i.intValue();
        }

        System.out.println("개수 : " + set.size());
        System.out.println("총합 : " + total);
        System.out.println("가장 큰 값 : " + Collections.max(values));
        System.out.println("가장 작은 값 : " + Collections.min(values));
    }
}
A 1
B 3
C 2
D 4
E 9
All keys: [A, B, C, D, E]
개수 : 5
총합 : 19
가장 큰 값 : 9
가장 작은 값 : 1

Example2

전화번호부

키와 값을 모두 Object 형태로 저장하기 때문에 HashMap의 값으로 HashMap을 저장할 수 있다.

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

public class Main {
    static Map<String, HashMap<String, String>> map = new HashMap<>();

    public static void main(String[] args) {
        addContact("A", "010-1111-1111");
        addContact("친구", "B", "010-7979-7979");
        addContact("친구", "C", "010-2222-2222");
        addContact("회사", "D", "010-4444-4444");
        addContact("가족", "E", "010-9090-9999");
        addContact("가족", "F", "010-1121-3333");
        addContact("가족", "G", "010-6531-8888");

        getList();
    }

    static void addGroup(String groupName) {
        if (!map.containsKey(groupName)) {
            map.put(groupName, new HashMap<String, String>());
        }
    }

    static void addContact(String groupName, String name, String tel) {
        addGroup(groupName); //
        HashMap<String, String> group = map.get(groupName); // value(HashMap) of group
        group.put(tel, name); // unique key = tel
    }

    static void addContact(String name, String tel) {
        addContact("기타", name, tel);
    }

    static void getList() {
        for (Map.Entry<String, HashMap<String, String>> e : map.entrySet()) {
                                                // Entry ← Set (Group)
            print("=== " + e.getKey() + "[" + e.getValue().size() + "] ===");

            for (Map.Entry<String, String> valE : e.getValue().entrySet()) {
                                      // Entry ← Set (HashMap(Name, Tel) to Group)
                String tel = (String) valE.getKey();
                String name = (String) valE.getValue();
                print(name + " " + tel);
            }
        }
    }

    public static void print(String str) {
        System.out.println(str);
    }
}
=== 가족[3] ===
E 010-9090-9999
F 010-1121-3333
G 010-6531-8888
=== 기타[1] ===
A 010-1111-1111
=== 친구[2] ===
B 010-7979-7979
C 010-2222-2222
=== 회사[1] ===
D 010-4444-4444

HashMap API

참고 서적: 자바의 정석 3판 2