안녕하세요! 오늘은 Java의 Map 인터페이스를 구현한 여러 클래스들의 차이점을 깊이 있게 알아보려고 합니다. Map은 자료구조의 꽃이라고 할 수 있는데요, 각각의 특징과 장단점을 이해하면 우리가 만드는 프로그램의 성능을 크게 향상시킬 수 있습니다.
Map 구현체 비교
구현체 | 검색 속도 | 정렬 여부 | 스레드 안전 | 주 사용처 |
---|---|---|---|---|
HashMap | O(1) | ❌ | ❌ | 일반적인 상황 |
TreeMap | O(log n) | ✅ | ❌ | 정렬이 필요한 경우 |
ConcurrentHashMap | O(1) | ❌ | ✅ | 멀티쓰레드 환경 |
1. HashMap - 가장 기본적인 Map
HashMap은 가장 일반적으로 사용되는 Map 구현체입니다. 내부적으로 '해시 테이블'이라는 자료구조를 사용하여 데이터를 저장합니다.
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성
Map<String, Integer> scores = new HashMap<>();
// 데이터 추가
System.out.println("=== 점수 입력 ===");
scores.put("김철수", 95);
scores.put("이영희", 88);
scores.put("박민수", 76);
scores.put("김철수", 92); // 동일한 키는 값이 덮어써짐
// 데이터 조회
System.out.println("=== 점수 조회 ===");
System.out.println("김철수의 점수: " + scores.get("김철수")); // 92
System.out.println("존재하지 않는 학생: " + scores.get("홍길동")); // null
// 데이터 순회
System.out.println("=== 전체 점수 출력 ===");
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.printf("%s: %d점%n", entry.getKey(), entry.getValue());
}
// 데이터 삭제
scores.remove("박민수");
System.out.println("=== 박민수 삭제 후 크기: " + scores.size());
}
}
2. TreeMap - 정렬이 필요할 때
TreeMap은 이진 검색 트리(Red-Black Tree) 구조를 사용하여 키를 기준으로 정렬된 상태를 유지합니다.
public class TreeMapExample {
public static void main(String[] args) {
// TreeMap 생성
Map<Integer, String> ranking = new TreeMap<>();
// 무작위로 데이터 추가
System.out.println("=== 랭킹 입력 ===");
ranking.put(3, "김철수");
ranking.put(1, "이영희");
ranking.put(2, "박민수");
// 자동으로 키 순으로 정렬되어 출력됨
System.out.println("=== 순위표 출력 ===");
for (Map.Entry<Integer, String> entry : ranking.entrySet()) {
System.out.printf("%d등: %s%n", entry.getKey(), entry.getValue());
}
// TreeMap만의 특별한 메서드들
TreeMap<Integer, String> treeMap = (TreeMap<Integer, String>) ranking;
System.out.println("=== 특별한 기능들 ===");
System.out.println("가장 낮은 등수: " + treeMap.lastKey()); // 3
System.out.println("가장 높은 등수: " + treeMap.firstKey()); // 1
System.out.println("2등 바로 다음 등수: " + treeMap.higherKey(2)); // 3
System.out.println("2등 바로 이전 등수: " + treeMap.lowerKey(2)); // 1
}
}
3. ConcurrentHashMap - 멀티쓰레드 환경에서
여러 쓰레드가 동시에 Map을 수정할 때 발생할 수 있는 문제를 해결해주는 특별한 Map입니다.
public class ConcurrentHashMapExample {
public static void main(String[] args) {
// ConcurrentHashMap 생성
Map<String, Integer> onlineUsers = new ConcurrentHashMap<>();
// 여러 쓰레드에서 동시에 접근해도 안전
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
onlineUsers.compute("총 접속자", (key, oldValue)
-> (oldValue == null) ? 1 : oldValue + 1);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
onlineUsers.compute("총 접속자", (key, oldValue)
-> (oldValue == null) ? 1 : oldValue + 1);
}
});
// 쓰레드 실행
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 결과 확인 - 항상 2000이 나옴
System.out.println("최종 접속자 수: " + onlineUsers.get("총 접속자"));
}
}
각 Map 구현체는 언제 사용해야 할까요?
- HashMap을 사용해야 할 때
- 단순히 키-값 쌍을 저장하고 검색할 때
- 정렬이 필요 없고, 단일 쓰레드 환경일 때
- 가장 빠른 읽기/쓰기 성능이 필요할 때
- TreeMap을 사용해야 할 때
- 키를 기준으로 정렬된 상태를 유지해야 할 때
- 특정 범위의 키를 검색해야 할 때
- 가장 크거나 작은 키를 자주 조회해야 할 때
- ConcurrentHashMap을 사용해야 할 때
- 여러 쓰레드가 동시에 Map을 수정하는 환경일 때
- 데이터의 정합성이 매우 중요할 때
- 읽기 작업이 쓰기 작업보다 많은 경우
주의사항
- HashMap과 ConcurrentHashMap에서의 Key 객체
- equals()와 hashCode() 메서드를 반드시 올바르게 구현해야 합니다.
- hashCode()가 잘못 구현되면 성능이 크게 저하될 수 있습니다.
- TreeMap에서의 Key 객체
- Comparable 인터페이스를 구현하거나 Comparator를 제공해야 합니다.
- 정렬 순서가 명확해야 합니다.
- ConcurrentHashMap 사용 시
- 불필요한 경우 사용하지 않는 것이 좋습니다. (약간의 성능 오버헤드 발생)
- 복잡한 동시성 요구사항이 있다면 추가적인 동기화가 필요할 수 있습니다.
마치며
이렇게 각각의 Map 구현체는 서로 다른 특징과 장단점을 가지고 있습니다. 여러분의 요구사항에 맞는 적절한 Map을 선택하시면 됩니다. 처음에는 가장 기본적인 HashMap을 사용하다가, 정렬이 필요하거나 멀티쓰레드 환경이 필요한 경우 다른 구현체로 전환하는 것을 추천드립니다.
'개발일지' 카테고리의 다른 글
자바스크립트 화살표 함수 vs 일반 함수: 당신이 알아야 할 모든 것 (0) | 2024.11.30 |
---|---|
[TIL] 자바스크립트 옵셔널 체이닝 연산자(?.) 완벽 가이드 (0) | 2024.11.28 |
Java Set: HashSet, TreeSet, LinkedHashSet 완벽 가이드 (0) | 2024.11.28 |
[pre-project 문제해결 공유] @RestController 어노테이션과 @Validated 어노테이션 충돌문제 (0) | 2023.08.22 |
[pre-project 회고] 경험치를 올려 레벨업을 했다 feat.코드스테이츠 (0) | 2023.06.30 |