SW/Java

Java의 Map.of()와 New HashMap() 비교: 성능과 이점

얇은생각 2024. 8. 10. 07:30
반응형

Java는 웹, 모바일, 데스크톱 애플리케이션을 개발하는 데 널리 사용되는 프로그래밍 언어입니다. 이 언어는 개발자가 프로그램에서 사용할 수 있는 다양한 유용한 데이터 구조를 제공합니다. 그중 Map 인터페이스는 데이터가 키-값 쌍으로 저장되어 많은 애플리케이션에 필수적인 데이터 구조입니다. 이 글에서는 Java에서 Map.of()와 new HashMap<>를 사용하는 방법, 이 둘의 차이점, 그리고 Map.of()를 사용하는 이점에 대해 자세히 살펴보겠습니다.

 

Java의 Map.of()와 New HashMap() 비교: 성능과 이점

 

Map.of()란 무엇인가?

Map.of()는 Java 9에서 도입된 메서드로, 최대 10개의 키-값 쌍으로 구성된 불변의 맵을 생성할 수 있습니다. 이 메서드는 소규모 맵을 간편하고 간결하게 생성할 수 있는 방법을 제공하여, 기존에 HashMap 클래스의 생성자를 사용해 소규모 맵을 만드는 것보다 코드 작성이 수월해졌습니다.

 

 

Map.of()의 장점

  1. 간결함: Map.of()는 작은 맵을 생성할 때 코드를 간결하게 작성할 수 있도록 합니다. 이로 인해 코드가 더 읽기 쉽고 유지보수도 용이합니다.
  2. 불변성: Map.of()로 생성된 맵은 불변(immutable)이며, 생성 후에는 수정할 수 없습니다. 이는 맵에 저장된 데이터의 안전성과 보안을 높여줍니다.
  3. 타입 안전성: Map.of()는 맵의 키와 값에 대한 타입 안전성을 제공하여, new HashMap<>()를 사용할 때 발생할 수 있는 타입 관련 오류를 방지합니다.

 

 

New HashMap()란 무엇인가?

new HashMap<>()는 Java의 HashMap 클래스에서 제공하는 생성자로, 새로운 HashMap 인스턴스를 생성합니다. 이 방법은 가변 맵을 생성하며, 생성된 맵은 키-값 쌍을 추가, 제거, 업데이트하여 수정할 수 있습니다. HashMap은 특히 더 많은 데이터 집합을 처리할 때 자주 사용됩니다.

 

HashMap의 장점

  1. 유연성: HashMap은 가변적이기 때문에 데이터의 추가, 수정, 삭제가 가능합니다. 이는 데이터의 크기가 변동할 수 있는 경우에 유리합니다.
  2. 성능: HashMap은 대량의 데이터를 처리할 때 높은 성능을 발휘하며, 특히 삽입과 검색 작업에서 효율적입니다.
  3. 동기화되지 않음: HashMap은 기본적으로 동기화되지 않으므로, 단일 스레드 환경에서 더 빠르게 작동합니다. 그러나 멀티스레드 환경에서는 추가적인 동기화가 필요할 수 있습니다.

 

 

Map.of()와 New HashMap()의 성능 비교

성능을 비교하기 위해 Map.of()와 new HashMap<>()를 사용하여 맵을 생성하고, 다양한 연산을 수행하는 데 걸리는 시간을 측정할 수 있습니다. 아래는 Java에서 두 방법을 사용하여 맵을 생성하고 값을 가져오는 작업에 대한 벤치마크 코드입니다.

package ca.bazlur;

import org.openjdk.jmh.annotations.Benchmark;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@State(Scope.Benchmark)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 20, time = 1)
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@OperationsPerInvocation
public class MapBenchmark {
    private static final int SIZE = 10;

    private Map<Integer, String> mapOf;
    private Map<Integer, String> hashMap;

    @Setup
    public void setup() {
        mapOf = Map.of(
                0, "value0",
                1, "value1",
                2, "value2",
                3, "value3",
                4, "value4",
                5, "value5",
                6, "value6",
                7, "value7",
                8, "value8",
                9, "value9"
        );

        hashMap = new HashMap<>();

        hashMap.put(0, "value0");
        hashMap.put(1, "value1");
        hashMap.put(2, "value2");
        hashMap.put(3, "value3");
        hashMap.put(4, "value4");
        hashMap.put(5, "value5");
        hashMap.put(6, "value6");
        hashMap.put(7, "value7");
        hashMap.put(8, "value8");
        hashMap.put(9, "value9");
    }

    @Benchmark
    public void testMapOf(Blackhole blackhole) {
        Map<Integer, String> map = Map.of(
                0, "value0",
                1, "value1",
                2, "value2",
                3, "value3",
                4, "value4",
                5, "value5",
                6, "value6",
                7, "value7",
                8, "value8",
                9, "value9"
        );
        blackhole.consume(map);
    }


    @Benchmark
    public void testHashMap(Blackhole blackhole) {
        Map<Integer, String> hashMap = new HashMap<>();
        hashMap.put(0, "value0");
        hashMap.put(1, "value1");
        hashMap.put(2, "value2");
        hashMap.put(3, "value3");
        hashMap.put(4, "value4");
        hashMap.put(5, "value5");
        hashMap.put(6, "value6");
        hashMap.put(7, "value7");
        hashMap.put(8, "value8");
        hashMap.put(9, "value9");
        blackhole.consume(hashMap);
    }

    @Benchmark
    public void testGetMapOf() {
        for (int i = 0; i < 10; i++) {
            mapOf.get(i);
        }
    }

    @Benchmark
    public void testGetHashMap() {
        for (int i = 0; i < SIZE; i++) {
            hashMap.get(i);
        }
    }
}
 
 

벤치마크 결과

아래는 소규모 데이터 세트(예: 10개 아이템)를 사용한 벤치마크 결과입니다.

벤치마크모드횟수점수오류단위

MapBenchmark.testGetHashMap avgt 20 14.999 ±0.433 ns/op
MapBenchmark.testGetMapOf avgt 20 16.327 ±0.119 ns/op
MapBenchmark.testHashMap avgt 20 84.920 ±1.737 ns/op
MapBenchmark.testMapOf avgt 20 83.290 ±0.471 ns/op

이 결과는 HashMap이 Map.of()를 사용한 불변 맵보다 약간 더 빠른 get 연산을 제공하지만, Map.of()를 사용한 불변 맵의 생성이 HashMap 생성보다 더 빠르다는 것을 보여줍니다.

하지만 JDK 배포판이나 컴퓨터에 따라 벤치마크 결과가 조금 다를 수 있으며, 대부분의 경우 결과는 일관성을 유지합니다. 항상 직접 벤치마크를 실행하여 특정 사용 사례에 적합한 선택을 하는 것이 좋습니다.

 

 

마이크로 벤치마크 주의사항

  • 마이크로 벤치마크는 단순한 성능 비교로서, 최종 결정을 내리는 유일한 기준으로 사용되지 않아야 합니다.
  • 메모리 사용량, 스레드 안전성, 코드의 가독성 등 다른 요소들도 고려해야 합니다.

 

최종 의견

두 방법의 성능 차이는 대부분의 경우 크게 중요하지 않을 수 있습니다. 특히 간단한 시나리오에서는 Map.of()가 코드의 간결성과 짧은 길이 면에서 우위를 점할 수 있습니다. HashMap과 Map.of() 사이에서 선택할 때는 사용 사례, 코드의 간결함, 구성의 선호도(예: 가변성 또는 불변성)와 같은 다른 요소들을 고려해야 합니다.

 

 

결론

Map.of()는 Java 9에서 도입된 강력하고 유용한 메서드로, 작은 맵을 간결하게 생성할 수 있는 방법을 제공하며 불변성과 타입 안전성과 같은 추가 이점을 제공합니다.

벤치마크 결과에 따르면, 작은 맵의 경우 Map.of()와 new HashMap<>()의 지연 시간이 비슷하며, 오류 범위가 겹쳐져 있어 데이터만으로 한 메서드가 다른 메서드보다 훨씬 빠르다고 결론 내리기 어렵습니다.

Map.of()를 사용할 때의 이점에는 간결함, 불변성, 타입 안전성이 포함됩니다. 성능 차이는 제공된 벤치마크 결과에 크게 영향을 미치지 않을 수 있지만, Map.of()의 다른 장점들은 매력적인 선택지로 만듭니다.

개발자는 이러한 이점을 활용하여 작은 맵을 생성할 때 Map.of()를 고려하는 것이 좋습니다.

반응형