백기선님의 Effective Java 강의를 듣는 도중 다음과 같은 질문이 있었습니다.
enum을 key로 사용하는 Map을 정의하세요.
enum을 담고 있는 Set을 만들어 보세요.
이 질문에 자신 당당히 HashMap, HashSet을 생각했습니다. 그런데 난생 처음 듣는 EnumMap, EnumSet이 정답이었습니다.
EnumMap과 EnumSet은 HashMap, HashSet과는 어떠한 점이 차이가 있을까요?
EnumMap
EnumMap의 구조
public EnumMap(Class<K> keyType) {
this.keyType = keyType;
keyUniverse = getKeyUniverse(keyType);
vals = new Object[keyUniverse.length];
}
EnumMap의 경우 entry를 저장하는 내부 구조가 배열로 되어 있습니다.
반면 HashMap의 경우 해시테이블을 사용해 key를 저장하고, key에 매핑되는 value값을 가르키는 방식으로 저장됩니다.
이 때, key를 저장하는 위치는 hash함수에 key값을 대입해 나오는 주소가 됩니다.
일반적으로 hash함수를 통해 key가 저장될 위치를 계산하는 방식은 O(1)의 시간 복잡도를 갖지만, 해시 충돌이 발생하는 경우에는 O(nlog(n))의 시간 복잡도를 갖습니다.
EnumMap의 경우 해시충돌이 발생할 일이 없습니다. 그렇기 때문에 최악의 경우에도 O(1)의 시간 복잡도를 갖습니다.
EnumMap의 특징
- EnumMap은 HashMap 보다 빠르다,
- EnumMap은 Java Collections Framework중 하나이다.
- EnumMap은 순서가 유지된다.
- EnumMap 인스턴스의 모든 키들은 같은 enum type이어야 한다.
- EnumMap은 null key를 허용하지 않는다. (NullPointerException 발생)
- EnumMap은 성능 향상을 위해 내부적으로 배열로 표시된다.
EnumMap, HashMap의 속도 비교
public class MyEnumMap {
public static void main(String[] args) {
EnumMap<Days, String> enumMap = new EnumMap<>(Days.class);
init(enumMap);
HashMap<Days, String> hashMap = new HashMap<>();
init(hashMap);
System.out.println("<< Hash Map >>");
calculate(hashMap);
System.out.println("<< Enum Map >>");
calculate(enumMap);
}
private static void init(Map<Days, String> map) {
map.put(Days.MONDAY, "Monday");
map.put(Days.TUESDAY, "Tuesday");
map.put(Days.WEDNESDAY, "Wednesday");
map.put(Days.THURSDAY, "Thursday");
map.put(Days.FRIDAY, "Friday");
map.put(Days.SATURDAY, "Saturday");
map.put(Days.SUNDAY, "Sunday");
}
private static void calculate(Map<Days, String> map) {
long[] testCase = new long[100];
for (int i = 0; i < testCase.length; i++) {
long startTime = System.nanoTime();
for (int j = 0; j < 10_000_000; j++) {
map.get(Days.MONDAY);
}
long endTime = System.nanoTime();
testCase[i] = endTime - startTime;
}
long sum = 0;
for (final long l : testCase) {
sum += l;
}
System.out.println(sum / testCase.length);
}
enum Days {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
}
간단하게 EnumMap의 get() 메서드, HashMap의 get()메서드를 통해 평균적으로 시간이 얼마나 걸리는 지 테스트를 해봤습니다.
<< Hash Map >>
17399774
<< Enum Map >>
13236301
테스트 결과에서 알 수 있듯 EnumMap이 HashMap 보다 더 우월한 성능을 보여주었습니다.
EnumSet
EnumSet의 구조
EnumSet은 추상 클래스이며, 인스턴스 생성을 위한 정적 팩토리 메서드가 정의 되어 있습니다.
JDK에서는 EnumSet의 구현체인 RegularEnumSet, JumboEnumSet을 제공합니다.
그렇기 때문에 다음과 같이 enum의 원소 수에 따라 구현체를 다르게 선택합니다.
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
- RegularEnumSet
- 비트 벡터를 표현하기 위해 단일 long 자료형을 사용
- long의 각 비트는 enum 값을 나타냄
- long은 64비트 이므로 64개의 원소를 저장할 수 있음
- JumboEnumSet
- long 요소의 배열을 비트 벡터로 사용함
- 64개이상의 원소를 저장 가능
- RegularEnumSet과 비슷하게 작동 하지만, 저장된 배열 인덱스를 찾기 위해 몇가지 추가 계산을 수행
EnumSet은 내부적으로 bit vector를 사용하기 때문에 메모리 효율이나 성능면에서 뛰어나며 64bit를 넘어가는 경우도 대응할 수 있다는 장점이 있습니다. 또한 올바른 버킷을 찾기 위해 해시 코드를 계산할 필요가 없습니다.
EnumSet의 특징
- Java Collection Framework의 하나이며 synchronized 되지 않는다.
- EnumSet의 모든 요소는 단일 enumeration type에서 가져와야 한다.
- HashSet보다 속도가 빠르다.
- null개체를 허용하지 않는다.
- 만약 Null 객체를 넣으려고 할 경우 NullPointerException이 발생한다.
EnumSet, HashSet의 속도 비교
public class MyEnumSet {
public static void main(String[] args) {
EnumSet<Days> enumSet = EnumSet.allOf(Days.class);
init(enumSet);
HashSet<Days> hashSet = new HashSet<>();
init(hashSet);
System.out.println("<< Hash Set >>");
calculate(hashSet);
System.out.println("<< Enum Set >>");
calculate(enumSet);
}
private static void init(Set<Days> set) {
set.add(Days.MONDAY);
set.add(Days.TUESDAY);
set.add(Days.WEDNESDAY);
set.add(Days.THURSDAY);
set.add(Days.FRIDAY);
set.add(Days.SATURDAY);
set.add(Days.SUNDAY);
}
private static void calculate(Set<Days> set) {
long[] testCase = new long[100];
for (int i = 0; i < testCase.length; i++) {
long startTime = System.nanoTime();
for (int j = 0; j < 10_000_000; j++) {
set.contains(Days.MONDAY);
}
long endTime = System.nanoTime();
testCase[i] = endTime - startTime;
}
long sum = 0;
for (final long l : testCase) {
sum += l;
}
System.out.println(sum / testCase.length);
}
enum Days {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
}
간단하게 EnumSet의 contains() 메서드, HashMap의 contains()메서드를 통해 평균적으로 시간이 얼마나 걸리는 지 테스트를 해봤습니다.
<< Hash Set >>
18159201
<< Enum Set >>
10377190
테스트 결과에서 알 수 있듯 EnumSet이 HashSet 보다 더 우월한 성능을 보여주었습니다.
[출처]
- https://velog.io/@wonny921/Java-EnumMap
- https://www.geeksforgeeks.org/difference-between-enummap-and-enumset-in-java/
'Java' 카테고리의 다른 글
[Java] 인터페이스 default method, static method (0) | 2024.01.18 |
---|---|
[Java] 함수형 인터페이스, 람다, 메소드 레퍼런스에 대하여 (1) | 2024.01.15 |
[Java] final 키워드 그리고 effectively final (0) | 2023.06.13 |
[Java] Exception의 종류 및 처리 방법 (0) | 2023.06.12 |
[Java] Object란 무엇인가요? (0) | 2023.06.02 |