HashMap, 어렵지 않게 사용하기

2020. 7. 6. 16:32Spring/Java

알고리즘을 공부하는 도중...

한 번 정리하고 가야겠다는 생각으로 시작하는 자바 클래스 포스팅

자바, 부셔보자 👊🏻

 

🗺 MAP ❓ 

자바에서의 Map 자료형은 ' key'과 '속성 value'로 이루어져 있습니다.

 

일상에서의 예시를 들자면, 사전과 비슷한데요.

예를 들어 영한사전에서 'apple'이라는 단어가 있다면, '사과'라는 의미를 갖고 있는 것을 찾을 수 있겠죠?

'apple'과 '사과'는 한 쌍으로 묶여있습니다. 

 

이처럼, Map 자료형도 key와 value가 한 쌍으로 묶여 저장되어있습니다.

마치 배열에서 정수 index를 참조하여 해당 index의 데이터를 참고하는 것과 비교해보면

index를 자신의 입맛에 맞는 타입으로 설정해서 데이터를 참고하는 것으로 보면 좀 더 이해가 쉬울 것 같아요 〰️

 

Map의 특징

Map의 특징을 몇 개 적어보자면, 아래와 같습니다.

 

- key는 값을 찾는 인덱스이기 때문에 중복이 불가능 Unique합니다.

- Map 자료형은 순서를 신경쓰지 않는다.

- 파이썬에서는 dictionary라고도 쓰인다.

 

 

Map의 종류

JAVA는 다양한 Map 자료형을 지원하는데, HashMap 뿐만 아니라 TreeMap과 LinkedHashMap도 있습니다.

저장방식의 차이인데요.

 

TreeMap 은 내부적으로 RedBlack Tree로 저장됩니다. 키값에 대한 Compartor 구현으로 정렬 순서를 바꿀수 있죠.

LinkedHashMap은 링크드 리스트로 저장됩니다. 그렇기 때문에 순서가 보장되죠.

마지막으로 HashMap은 아래에서 더욱 더 자세하게 다뤄보도록 하겠습니다.

 

🏜 HashMap

HashMap은 내부적으로 Entry<K,V>[] Entry 의 array 로 되어 있습니다.

해당 array에 index는 내부 해쉬 함수를 통해 계산됩니다.

 

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

// map.put(key, value)의 형식으로 map에 데이터 추가
map.put("Integer", "aa");
map.put("Float", "bb");
map.put("String", "cc");
map.put("Boolean", "dd");
map.put("Map", "aa");
map.put("HashMap", "bb");
map.put("TreeMap", "cc");
map.put("LinkedHashMap", "dd");

// map의 key들만 가져와서 순회 - keySet()은 Set자료형 반환
for (String s : map.keySet()) {
    System.out.println(s);
}

 

위와 같은 코드가 있을 때, 출력은 어떻게 될까요❓ 

 

Integer
Float
HashMap
String
Boolean
TreeMap
Map
LinkedHashMap

 

위와 같이 출력이 됩니다. 저장되었던 순서와는 상관없이 출력되는 것을 확인할 수 있습니다.

 

이제부터는 Map에서 사용할 수 있는 메소드를 알아보도록 하겠습니다.

 

.put (key, value)

✔️ 반환값 : void

 

put 메소드는 위의 예제에서 본 것 처럼 Map 자료형에 데이터를 추가할 수 있습니다.

 

 

.get (key) 

✔️ 반환값 : Object value

 

get 메소드는 key값을 참조해서 해당하는 value를 가져옵니다.

 

 

.containsKey (key)  

✔️ 반환값 : Boolean

 

containsKey 메소드는 Map 데이터에 key가 존재하는 지 확인하여 존재여부를 반환해줍니다.

예를 들어 위의 예제에서 아래의 코드를 추가한다면, Float라는 key 데이터를 추가하였기 때문에 true라는 boolean 타입의 데이터를 반환해줄 것입니다.

 

map.containsKey("Float"); // true
map.containsKey("float"); // false

 

 

.containsValue (value)

✔️ 반환값 : Boolean

 

위와 비슷하게 key가 아닌 value가 존재하는지 확인하는 메소드입니다.

위와 비슷한 맥락으로 사용해주시면 됩니다 〰️ 

 

 

.remove (key) 

✔️ 반환값 : Object value

 

remove 메소드는 해당하는 key의 데이터를 삭제합니다.

그리곤 key값에 해당하는 value를 출력해줍니다.

 

System.out.println(map.remove("Float")); // bb

 

위의 코드를 보면, "Float"이라는 키값에 해당하는 value인 bb가 출력되는 것을 확인할 수 있습니다.

 

 

.size () 

✔️ 반환값 : int

size는 map에 저장된 개수를 출력해줍니다.

 

System.out.println(map.size()); // 8

 

위의 예제에서 map.size()를 출력하면 8개가 출력되는 것을 확인할 수 있습니다.

 

 

 

 

🎡 Using More Efficiently

 

HashMap을 더욱 효율적으로 사용하기 위해서 'lambda expression' 를 사용해보도록 하겠습니다. 

 

.forEach (element -> {logic})

✔️ 반환값 : void

 

위의 예제에서는 모든 요소를 출력하기 위해서 for문을 사용했습니다. 

이번에는 forEach를 사용해보도록 해볼게요.

 

key 출력

 

// 기존 방식
for (String s : map2.keySet()) {
  System.out.println(s);
}
        
// forEach 사용
map.keySet().forEach(key -> System.out.println(key));

 

위와 같이 사용할 수 있습니다.

위의 예제에서는 key를 출력했는데요, 이 번엔 value를 출력해볼까요?

 

value 출력

 

var values = map.values();
values.forEach(value -> System.out.println(value));

// or
map.values().forEach(value -> System.out.println(value));

 

위와 같이 표현할 수 있습니다.

이번에는 key와 value를 함께 접근해보도록 하겠습니다.

 

key & value 출력

 

Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
  System.out.print("key: "+ entry.getKey());
  System.out.println(", Value: "+ entry.getValue());
}
        
// or (lambda expression)
map.forEach((key, value) -> {
  System.out.print("key: "+ key);
  System.out.println(", Value: "+ value);
});

 

.computeIfAbsent(key, (key) -> {})

map 데이터에 key값을 가진 쌍이 있는지 확인합니다.

만약 없다면 다음 인자에 있는 함수 로직을 실행합니다.

함수의 return 값을 value로 map에 데이터를 삽입합니다.

 

피보나치로 예시를 한 번 들어볼까요❓

아래의 예제를 보면 memizeHashMap을 사용합니다.

메모이제이션(memoization)을 사용해서 피보나치를 더 효율적으로 만들어주기 위함이죠.

메모이제이션(memoization)은 중복된 값이 발생할 가능성이 있는 경우, 계산 결과를 미리 저장해두어 필요할 때 사용하는 기술입니다.

 

import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
public class Fibonacci {
    private Map<Integer, BigInteger> memoizeHashMap = new HashMap<>();
    {
        memoizeHashMap.put(0, BigInteger.ZERO);
        memoizeHashMap.put(1, BigInteger.ONE);
        memoizeHashMap.put(2, BigInteger.ONE);
    }
    private BigInteger fibonacci(int n) {
        if (memoizeHashMap.containsKey(n)) {
            return memoizeHashMap.get(n);
        } else {
            BigInteger result = fibonacci(n - 1).add(fibonacci(n - 2));
            memoizeHashMap.put(n, result);
            return result;
        }
    }
    public static void main(String[] args) {
        Fibonacci fibonacci = new Fibonacci();
        for (int i = 0; i < 100; i++) {
            System.out.println(fibonacci.fibonacci(i));
        }
    }
}

 

위의 코드에서 fibonacci 메소드 확인해보면 n이라는 키의 여부에 따라 분기처리를 해주었습니다.

이 때, computeIfAbsent 메소드를 사용하면 간결하게 사용할 수 있습니다.

 

private BigInteger fibonacci (int n) {
  return memoizeHashMap.computeIfAbsent(n, 
           (key) -> fibonacci(n - 1).add(fibonacci(n - 2)));
}

위의 코드처럼요❗️ 

상당히 간단해졌죠?

 

비슷하게 putIfAbsent(key, ()->{}) 메소드도 있는데요.차이점은 putIfAbsent() 는 key값이 존재해도 다음 인자로 들어온 함수를 실행합니다. 

 

더 자세한 사항은 이 곳을 참조하세요 ❗️ 

 

 

 

.getOrDefault(key, default value)

key 값을 확인해서 있다면, 해당하는 value를 없다면 두 번째 인자를 반환합니다.

 

 

 

오늘은 HashMap 자료형에 대해 알아보았는데요.

다음에도 더 많은 자료형으로 포스팅해보아야겠어요~.~

 

'Spring > Java' 카테고리의 다른 글

Masking Name with Regex, in java  (0) 2022.05.29
Java Date & Time, 제대로 사용하기  (2) 2022.05.22
ENUM, Clean Code with Java  (0) 2022.05.11
Matcher, 어렵지 않게 사용하기  (2) 2020.08.24
Pattern, 어렵지 않게 사용하기  (2) 2020.08.18