해당 포스트는 "자바 성능 튜닝 이야기" 책의 내용을 요약한 것이다.



※ Set 인터페이스

: 중복을 허용하지 않는 집합을 처리하기 위한 인터페이스

- HashSet : 데이터를 해쉬 테이블에 담는 클래스로 순서 없이 저장된다.

- TreeSet : red-black 트리에 데이터를 담으면서 값에 따라 정렬을 해 순서가 정해진다. 따라서 HashSet 보다 성능상 느리다.

*red-black tree : 이진 탐색 트리에서 양쪽의 균형을 맞추는 기능을 추가한 트리이다. 트리의 한 쪽으로 균형이 쏠릴 경우 트리의 성능이 떨어지는 것을 방지한다.

1. LinkedHashSet : 저장된 순서에 따라 해쉬 테이블에 데이터를 담는다.


2. Set.add(data)를 통해서 데이터를 넣을 때

: HashSet과 LinkedHashSet의 성능은 비슷하고, TreeSet은 정렬을 해야 되서 TreeSet의 성능이 더 안 좋다. 만약 Set 객체에 담을 데이터의 갯수를 알고 있다면, 예를 들어 HastSet set = new HashSet<String>(COUNT); 라면 성능상 큰 차이는 발생하지 않으나 데이터의 크기를 알 경우 성능상 더 유리하다.


3. Set.iterator을 통해 데이터를 읽거나 Set 객체 안에 있는 임의의 데이터가 존재하는 지 확인할 때

: HashSet과 LinkedHashSet의 속도는 빠르지만, TreeSet은 느리다.


- 느린 TreeSet을 만든 이유?

: TreeSet은 다른 Set 콜렉션보다 느리다. 이렇게 느린 TreeSet을 만든 이유는 데이터를 저장하면서 정렬하기 때문이다. TreeSet은 NavigableSet<E> 인터페이스를 구현한다. 해당 인터페이스는 특정 값보다 큰 값, 작은 값, 가장 큰 값 등을 추출하는 메서드가 있다. 즉, 데이터를 순서에 따라 탐색하는 작업이 필요할 때 사용하면 좋다.


*Set 콜렉션에서는 데이터를 순서에 따라 탐색하는 작업을 하면 TreeSet을 사용하되, 그렇지 않으면 HashSet이나 LinkedHashSet을 사용해라.



※ List 인터페이스

: 배열의 확장판으로 데이터를 담을 수 있는 크기가 자동으로 증가되 데이터 개수를 확실히 모를 때 유용하다.
- Vector : 크기를 지정할 필요가 없는 배열 클래스이다.
- ArrayList : Vector와 비슷하지만 동기화 처리가 되어 있지 않다.
- LinkedList : ArrayList와 동일하지만 Queue 인터페이스를 구현했기 때문에 FIFO 큐 작업을 수행한다.

1. List.add(data)로 데이터를 넣을 때
: 데이터를 넣는 속도는 어떤 클래스든 차이가 없다. 

2. List.get(index)를 통해 데이터를 가져올 때
: ArrayList의 속도가 가장 빠르다. Vector와 LinkedList는 속도가 매우 느리다. LinkedList는 Vector 보다 속도가 터무니 없이 느리다. LinkedList가 매우 느린 이유는 Queue 인터페이스를 상속받기 때문이다. 따라서 LinkedList는 데이터를 가져올 때 LinkedList.get(index)가 아닌 peek() 메서드를 사용해야 한다. peek()를 사용하면 속도가 ArrayList와 속도면에서 차이가 없다. 따라서 LinkedList는 get메서드가 아닌 peek()나 poll()을 사용해야 한다. ArrayList와 Vector의 성능 차이가 큰 이유는 동기화에 있다. ArrayList는 동기화 처리가 되어 있지 않아 빠른 대신 여러 스레드에서 접근할 경우 문제가 발생한다. 반면에, Vector의 경우 get() 메서드에 synchronized가 선언되 있어 여러 스레드의 접근을 방지한다. 그래서 성능이 저하될 수 밖에 없다. 

3. List.remove()을 통해 데이터를 삭제할 때
: remove() 메서드를 통해 데이터를 삭제할 때 컬렉션의 첫 번째 데이터를 삭제하느냐와 마지막 데이터를 삭제하느냐에 따라 성능이 달라진다. 먼저 첫 번째 데이터를 삭제 할 때는 ArrayList와 LinkedList의 속도는 비슷하고 Vector는 느리다. 마지막 데이터를 삭제할 때는 ArrayList의 속도가 가장 빠르고 Vector와 LinkedList 속도는 느리다. 그렇다면 첫 번째 데이터를 삭제할 때와 마지막 데이터를 삭제할 때를 비교해보자. ArrayList와 Vector의 경우 마지막 데이터를 삭제할 때가 훨씬 빠르다. LinkedList는 비슷하다. 그 이유는 ArrayList와 Vector는 첫 번째 데이터를 삭제하면 뒤에 있는 데이터가 첫 번째 인덱스로 와야하고 다음 인덱스는 두 번째 인덱스로 와야한다. 즉, 마지막 값까지 위치를 바꿔야 한다. 그래서 ArrayList와 Vector의 경우 첫 번째 값을 삭제하면 느릴 수 밖에 없다. 


※ Map 인터페이스

: Key와 Value의 쌍으로 저장되는 구조체이다.
- Hashtable : 데이터를 해쉬 테이블에 담는다. 동기화 처리가 되있어, 동기화가 필요한 부분에서는 이 클래스를 사용한다.
- HashMap : 데이터를 해쉬 테이블에 담는다. Hashtable 클래스와 다른 점은 null값을 허용하고 동기화되어 있지 않다는 것이다.
- TreeMap : red-black 트리에 데이터를 담는다. TreeSet과 다른 점은 키에 의해서 순서가 정해진다는 것이다.
- LinkedHashMap : HashMap과 거의 동일하며 이중 연결리스트 방식을 사용하여 데이터를 담는다.

1. Map.get()을 통해 데이터를 꺼낼 때
: Map.put()을 통해 데이터를 넣을 때는 성능이 거의 동일하다. get()을 통해 데이터를 꺼낼 때는 TreeMap이 정렬 때문에 가장 느리고 Hashtable이 동기화 문제로 조금 느리다. 나머지는 속도가 비슷하다. 


* Set, List, Map 컬렉션들은 각 클래스의 용도가 다르다. 따라서 필요한 용도에 가장 적합한 클래스를 선택하여 사용하는 게 가장 적절하다. 각 인터페이스별로 가장 일반적으로 사용되는 클래스는 HashSet, ArrayList, HashMap이다. Queue 인터페이스의 경우 LinkedList 이다. 

※ Collection 클래스의 동기화

: Vector와 Hashtable을 제외한 클래스들은 동기화되지 않은 클래스이다. 이들 클래스에 대해 동기화를 지원할 수 있는다. 방법은 synchronized로 시작하는 synchronizedSet/List/Map 메서드를 사용하는 것이다. 다음과 같다.

Set s = Collections.synchronizedSet(new HashSet(...));
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));
Set s = Collections.synchronizedSet(new LinkedHashSet(...));

List list = Collections.synchronizedLsit(new Array(또는 Linked)List(...));

Map m = Collections.synchronizedMap(new Hash(Tree/LinkedHash)Map(...));

위 synchronized 처리한 클래스들은 Vector와 Hashtable보다 성능이 좋다.  Map의 경우에 ConcurrenHashmap을 이용하면 synchronized 처리한 Hashmap 보다 더 나은 성능을 낼 수 있다. 

+ Recent posts