Java Map관련 클래스의 비교
Map Class
Java Collection Framework 중 Map Class에 대해서 이야기해 보려고 합니다.
Map은 Key-Value 한 쌍으로 이루어진 집합이며, 키의 중복은 허용되지 않으며 값은 중복이 됩니다.(모든 Map인터페이스를 상속받은 클래스가 허용하지는 않음) 그리고 순서를 보장하지 않습니다. Map interface를 상속받아 사용 중인 아래 세 가지 클래스를 알아보려 합니다.
- 종류
- HashMap: Key의 중복은 허용하지 않으며, Value의 중복은 허용합니다. Key와 Value의 null값을 저장할 수 있습니다. 해싱기법으로 데이터를 찾기때문에 검색에 유리합니다. 동기화는 지원되지 않습니다.
- HashTable: HashMap과 동일하게 Key 중복은 허용하지 않으며, Value의 중복은 허용합니다. HashMap과 다른 점은 동기화(synchronize)를 지원합니다.
- TreeMap: 데이터 삽입 시, 내부적으로 정렬을 지원합니다. Key를 기준으로 정렬을 수행하기 때문에 Key에 null값을 저장할 수 없습니다. Key의 중복은 허용하지 않으며, Value의 중복은 허용합니다. HashMap과 동일하게 동기화는 지원되지 않습니다.
HashMap, TreeMap 동기화 처리는 개발자가 동기화 처리를 적용해주어야 한다고 명시되어 있습니다.
Note that this implementation is not synchronized. If multiple threads access a linked list concurrently, and at least one of the threads modifies the list structurally, must be synchronized externally
중복 및 NUll, 동기화 테스트
아래는 Key, Value의 중복 테스트입니다. HashMap, HashTable, TreeMap 모두 Key는 중복을 허용하지 않으며 Value 값이 덮어쓰여지는 것을 볼 수 있습니다. Value는 중복을 허용하므로 같은 Value가 저장된 것을 확인 할 수 있습니다.
Map<String, Integer> hashMap = new HashMap<>();
Map<String, Integer> hashTable = new Hashtable<>();
Map<String, Integer> treeMap = new TreeMap<>();
hashMap.put("a", 1);
hashMap.put("a", 2);
hashMap.put("b", 1);
hashTable.put("a", 1);
hashTable.put("a", 2);
hashTable.put("b", 1);
treeMap.put("a", 1);
treeMap.put("a", 2);
treeMap.put("b", 1);
System.out.println("hashMap: " + hashMap.toString());
System.out.println("hashTable: " + hashTable.toString());
System.out.println("treeMap: " + treeMap.toString());
아래는 Key, Value의 null값 테스트입니다. hashMap은 모두 null을 저장할 수 있으며 hashTable은 모두 null값을 저장할 수 없습니다. treeMap은 Key기준으로 정렬하기 때문에 Key에 대해선 null값을 저장할 수 없습니다.
...
hashMap.put(null, null); // => {null=null}
hashTable.put("a", null); // => java.lang.NullPointerException 발생
hashTable.put(null, 1); // => java.lang.NullPointerException 발생
treeMap.put(null, 1); // => Key 기준으로 정렬하기 때문에 java.lang.NullPointerException 발생
...
HashMap, HashTable의 동기화 테스트입니다. 스레드 5개를 만들어 각 스레드마다 1000개의 데이터를 Map에 삽입하는 테스트를 진행했습니다. 결과는 HashMap은 동기화를 보장하지 않으므로 size가 1000개를 벗어나는 것을 볼 수 있습니다. HashTable은 put 메서드에 synchronized가 적용되었기 때문에 동기화가 보장되는 것을 볼 수 있습니다.
- HashMap 동기화 테스트
public static void main(String[] args) {
Map<String, Integer> hashMap = new HashMap<>();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
hashMap.put(String.format("Apple%s", j), j);
}
System.out.println(hashMap.size());
}).start();
}
}
- HashTable 동기화 테스트
public static void main(String[] args) {
Map<String, Integer> hashTable = new Hashtable<>();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
hashTable.put(String.format("Banana%s", j), j);
}
System.out.println(hashTable.size());
}).start();
}
}
위에서 설명한 내용들을 검증하기 위해 테스트를 진행했습니다. 테스트 깃헙 링크
결론
Key-Value형식의 데이터를 저장할 때, Collections 패키지의 Map을 사용하되 동기화가 필요하다면 HashTable보다는 java1.5부터 지원하는 ConcurrentHashMap을 사용하는 것이 좋을 것 같습니다. HashTable은 synchronized를 메서드 방식으로 사용하여 HashTable 객체에 락을 걸어 모든 synchronized 메서드가 락이 되어 속도 이슈가 생길 수 있습니다. ConcurrentHashMap은 블록 방식으로 동기화가 필요한 Object만 synchronized을 적용하기 때문에 HashTable보다 속도 측면에서 유리합니다.
Leave a comment