본문 바로가기
Old Posts/Java

[Java] HashMap 사용법 및 예제

by A6K 2021. 6. 10.

HashMap

자바에서 제일 많이 사용되는 컬렉션 객체 중 하나가 바로 HashMap 일 것이다. HashMap은 Map 인터페이스를 구현한 대표적인 클래스로 Map의 특성을 그대로 이어받았다. HashMap은 키와 값으로 구성된 엔트리(Entry) 객체를 저장하는 자료구조로 키 객체의 해시코드를 이용해서 해시테이블을 탐색, 값을 가져오는 동작을 한다. HashMap에서 동일한 키에 대응되는 값은 하나만 저장할 수 있다. 따라서 동일한 키에 대응되는 값을 여러번 put() 메소드로 추가하면, 기존의 값은 덮어쓰여지고 마지막에 추가한 값이 살아남게 된다.

HashMap은 이름에서도 알 수 있듯이 해시코드를 이용해서 값을 탐색한다. 따라서 특정 키에 해당하는 값을 빠르게 찾아올 수 있다는 장점이 있다.

HashMap 사용법

HashMap 선언

HashMap<String, String> map1 = new HashMap<>();
HashMap<String, String> map2 = new HashMap<>(otherMap); // 다른 컬렉션으로부터 초기화
HashMap<String, String> map3 = new HashMap<>(10); // 초기 용량 설정
HashMap<String, String> map4 = new HashMap<>(10, 0.8f); // 초기 용량 및 load factor 설정

HashMap을 생성할 때, 어떤 데이터를 저장할 것인지 '지네릭스(Generics)'를 이용해서 지정해줘야한다. 초기화를 하지 않으면 빈 HashMap이 생성된다. HashMap 생성시 다른 HashMap이나 컬렉션을 인자로 줄 수 있다. 이런 경우 HashMap을 생성하면서 다른 컬렉션에 있던 데이터를 새로 생성된 HashMap에 저장해준다.

HashMap에 데이터를 추가하게 되면, 특정 시점에 해시테이블이 꽉차게 된다. 이 경우 새로운 해시테이블을 생성하고, 기존에 있던 데이터들을 옮기는 작업을 수행한다. 이 과정은 불필요한 데이터 복사를 발생시키므로 성능을 저하시킬 수 있다. 만약 처음부터 큰 해시테이블을 생성한다면 이런 작업은 피할 수 있다. HashMap 객체를 초기화하는데 생성자의 인자로 숫자를 주면, 그 만큼을 저장할 수 있는 해시테이블을 처음부터 생성하게 되므로 불필요한 데이터 복사를 피할 수 있다.

load factor는 Threshold 값을 계산하는데 사용된다. 용량 * loadFactor 값이 해시테이블의 Threshold 값이 되며, 해시테이블에 저장된 데이터의 개수가 이 값을 초과하면 리사이즈 동작을 진행한다.

HashMap 값 추가

HashMap에는 put() 메소드를 이용해서 값을 추가할 수 있다.

map.put("Key1", "Value1");
map.put("Key2", "Value2");
map.put("Key1", "Value3"); // Key1의 데이터는 Value3으로 덮어쓰여짐

put() 메소드에는 키와 값을 인자로 넣어줘야한다. 이 때, 인자로 넘겨주는 키와 값의 타입은 HashMap을 생성할 때 지네릭스로 명시한 타입을 사용해야한다.

만약 put() 메소드로 넘겨준 키가 이미 HashMap에 존재한다면, 기존에 저장되어 있던 값은 새로 입력된 값으로 대치된다.

HashMap 값 제거

HashMap에서 값을 제거하려면 remove() 메소드 혹은 clear()를 이용하면된다.

map.remove("Key1"); // Key1에 해당하는 데이터 삭제
map.remove("Key2"); // Key2에 해당하는 데이터 삭제

map.clear(); // 모든 데이터 삭제

remove() 메소드에 삭제할 데이터의 키를 넘겨주면, 그 키에 대응되는 엔트리를 삭제해준다. 그러면서 지워진 엔티리의 값을 리턴한다. 만약 키에 해당하는 엔트리가 HashMap에 없었으면 null이 리턴된다.

모든 데이터를 삭제하려면 clear() 메소드를 호출하면 된다.

HashMap 값 조회

HashMap에서 값을 조회하기 위해서는 get() 메소드를 사용하면 된다.

map.get("Key1"); // Key1에 해당하는 Value 리턴

입력받은 키 값에 해당하는 값이 리턴된다. 만약 키에 해당하는 엔트리가 존재하지 않으면 null이 리턴된다.

HashMap 순회

HashMap을 순회하는 방법에는 두 가지가 있다. KeySet을 이용한 방법과 EntrySet을 이용한 방법이다.

// EntrySet 이용
for (Entry<String, String> entry : map.entrySet()) {
  System.out.println("Key : " + entry.getKey());
  System.out.println("Value : " + entry.getValue());
}

// KeySet 이용
for (String key : map.keySet()) {
  System.out.println("Key : " + key);
  System.out.println("Value : " + map.get(key));
}

EntrySet의 경우 HashMap에 저장되어 있는 Entry 객체들을 반환한다. Entry 객체는 키와 값의 쌍(Pair)을 저장하는 객체로 getKey() 메소드와 getValue() 메소드로 각각 키와 값을 가져올 수 있다.

반대로 KeySet의 경우 HashMap에 저장되어 있는 키들을 리턴한다. 키를 받아서 get() 메소드로 키에 대응되는 값들을 호출해서 가져올 수 있다. 전체 HashMap 데이터 중 특정 키에 해당하는 값들을 가져오고 싶을 때 사용하면 좋다.

for each 구문 말고 Iterator를 이용해서 순회할 수도 있다.

Iterator <Entry<String, String>> entries = map.entrySet().iterator();
while(entires.hasNext()) {
  Entry<String, String> entry = entries.next();
  System.out.println("Key : " + entry.getKey());
  System.out.println("Value : " + entry.getValue());
}

Iterator <String> keys = map.keySet().iterator();
while(keys.hasNext()) {
  String key = keys.next();
  System.out.println("Key : " + key);
  System.out.println("Value : " + map.get(key));
}

 

 

생각해봐야 할 점은 KeySet을 이용해서 순회할 때, get() 메소드로 다시 해시테이블을 조회하는 오버헤드이다. 만약 전체 HashMap 데이터를 순회해야한다면, KeySet 대신 처음부터 EntrySet을 가져와서 사용하도록 하자. get() 메소드의 호출 오버헤드를 줄일 수 있다.

댓글