반응형
Map - Grouping 데이터 삽입 순서대로 안 돼요..
HashMap을 이용해서 Grouping 하여 데이터가 삽입된 순서대로 반환하려고 했는데, 데이터의 순서가 변경되어 반환되는 문제가 있었습니다.
해당 문제는 HashMap으로 Map 객체를 만들어 데이터의 삽입 순서가 보장되지 못한 문제였습니다.
Map<String, List<Category>> groupedCategory = Categories.stream()
.collect(Collectors.groupingBy(Category -> Category.type().get(locale)));
위 코드에서 데이터를 순서대로 그룹핑하기 위해 기본적으로 적용되는 Map 구현체가 아닌 LinkedHashMap을 사용하여 입력된 순서가 유지되도록 해야 합니다.
Map<String, List<Category>> groupedCategory = categories.stream()
.collect(Collectors.groupingBy(
Category -> Category.type().get(locale),
LinkedHashMap::new, // 입력된 순서대로 그룹핑 순서 유지
Collectors.toCollection(ArrayList::new))
));
다음은 HashMap, LinkedHashMap에 대해 알아보며 두 클래스의 성능 차이를 확인해 보겠습니다.
Map
Map 인터페이스는 Key-Value 쌍을 저장하는 자료구조입니다.
각 Key는 유일해야 하며, 특정 Key에 대해 매칭되는 Value를 반환합니다.
Map 주요 메서드
- put(key, value) : 특정 키에 값을 저장합니다.
- get(key) : 특정 키에 저장된 값을 조회합니다.
- containsKey(key) : 키 존재 여부를 확인합니다.
- remove(key) : 특정 키와 연결된 값을 제거합니다.
- isEmpty() : Map 안에 데이터가 존재하는지 확인합니다.
- size() : Map의 크기를 확인합니다.
HashMap
가장 기본적인 Map의 구현체로, Key-Value를 해시 테이블을 사용하여 저장합니다.
특징
- 순서를 보장하지 않는다.
- 데이터의 삽입 순서를 유지하지 않습니다.
- 빠른 접근 성능
- 해시 테이블 구조로 O(1)의 시간 복잡도로 데이터를 삽입하고 조회합니다.
주로 HashMap은 삽입 순서가 중요하지 않고, 빠른 검색과 삽입이 필요한 경우에 사용합니다.
HashMap hashMap = new HashMap<>();
hashMap.put("apple", "사과");
hashMap.put("banana", "바나나");
hashMap.put("tomato", "토마토");
System.out.println(hashMap.toString());
System.out.println("hashMap.size() = " + hashMap.size());
System.out.println("hashMap.isEmpty() = " + hashMap.isEmpty());
LinkedHashMap
HashMap을 확장한 구현체로, 데이터의 삽입 순서를 유지하며 Key-Value 쌍을 연결 리스트로 관리합니다.
특징
- 삽입 순서 유지
- 데이터가 삽입된 순서를 유지하며, 순회 시 삽입된 순서대로 데이터를 반환합니다.
- HashMap보다 느린 성능
- 연결 리스트를 사용하기 때문에 HashMap 보다 메모리 사용량이 높습니다.
Map<String, String> hashMap = new LinkedHashMap<>();
hashMap.put("apple", "사과");
hashMap.put("banana", "바나나");
hashMap.put("tomato", "토마토");
System.out.println(hashMap.toString());
System.out.println("hashMap.size() = " + hashMap.size());
System.out.println("hashMap.isEmpty() = " + hashMap.isEmpty());
HashMap vs LinkedHashMap 성능 분석
1. 데이터 삽입 및 검색 성능 테스트
private static final int DATA_SIZE = 1_000_000; // 테스트할 데이터 크기
public static void main(String[] args) {
System.out.println("Testing HashMap...");
Map<String, Integer> hashMap = new HashMap<>();
long hashMapInsertTime = testInsertPerformance(hashMap);
long hashMapSearchTime = testSearchPerformance(hashMap);
System.out.println("Testing LinkedHashMap...");
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
long linkedHashMapInsertTime = testInsertPerformance(linkedHashMap);
long linkedHashMapSearchTime = testSearchPerformance(linkedHashMap);
System.out.println("Results:");
System.out.println("HashMap Insert Time: " + hashMapInsertTime + " ms");
System.out.println("HashMap Search Time: " + hashMapSearchTime + " ms");
System.out.println("LinkedHashMap Insert Time: " + linkedHashMapInsertTime + " ms");
System.out.println("LinkedHashMap Search Time: " + linkedHashMapSearchTime + " ms");
}
// 데이터 삽입 성능 테스트 메서드
private static long testInsertPerformance(Map<String, Integer> map) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < DATA_SIZE; i++) {
map.put("key" + i, i);
}
return System.currentTimeMillis() - startTime;
}
// 데이터 검색 성능 테스트 메서드
private static long testSearchPerformance(Map<String, Integer> map) {
Random random = new Random();
long startTime = System.currentTimeMillis();
// 무작위 검색 성능 측정을 위해 데이터의 10%를 무작위로 조회
for (int i = 0; i < DATA_SIZE / 10; i++) {
int randomKeyIndex = random.nextInt(DATA_SIZE);
map.get("key" + randomKeyIndex);
}
return System.currentTimeMillis() - startTime;
}
HashMap과 LinkedHashMap의 데이터 삽입, 검색 성능을 보면 LinkedHashMap이 더 뛰어난 것을 확인할 수 있습니다.
→ JVM 최적화로 인해 두 구현체의 성능이 크게 차이가 나지 않는 것 같다. 또한 저장된 데이터가 정수형의 오름차순으로 삽입되어서 큰 차이가 발생하지 않은 것 같다.
결론 : 데이터 삽입 순서가 유지되어야 하면 LinkedHashMap을 사용해도 크게 성능 이슈가 있을 것 같지는 않다.
2. 메모리 사용량 테스트
private static final int DATA_SIZE = 1_000_000; // 테스트할 데이터 크기
public static void main(String[] args) {
System.out.println("Testing memory usage for HashMap...");
long hashMapMemoryUsage = testMemoryUsage(new HashMap<>());
System.out.println("Testing memory usage for LinkedHashMap...");
long linkedHashMapMemoryUsage = testMemoryUsage(new LinkedHashMap<>());
System.out.println("Memory Usage Results:");
System.out.println("HashMap Memory Usage: " + hashMapMemoryUsage / 1024 + " KB");
System.out.println("LinkedHashMap Memory Usage: " + linkedHashMapMemoryUsage / 1024 + " KB");
}
private static long testMemoryUsage(Map<String, Integer> map) {
// Garbage Collection 실행하여 초기 상태 클리어
System.gc();
// 삽입 전 메모리 사용량 측정
long beforeMemory = getUsedMemory();
// 데이터 삽입
for (int i = 0; i < DATA_SIZE; i++) {
map.put("key" + i, i);
}
// 삽입 후 메모리 사용량 측정
long afterMemory = getUsedMemory();
// 사용 메모리 계산
return afterMemory - beforeMemory;
}
private static long getUsedMemory() {
Runtime runtime = Runtime.getRuntime();
return runtime.totalMemory() - runtime.freeMemory();
}
HashMap과 LinkedHashMap의 메모리 사용량을 보면 LinkedHashMap이 더 많은 메모리를 사용했음을 알 수 있습니다.
→ 데이터의 삽입 순서를 유지하기 위해 LinkedHashMap은 메모리를 더 사용합니다.
반응형