Java Set관련 클래스의 비교

2 minute read

Set Class

예제1
Java Collection Framework 중 Set Class에 대해서 이야기해 보려고 합니다.
Set은 기본적으로 중복을 허용하지 않는 데이터의 집합으로 Java에서는 아래 세 가지 클래스가 Set interface를 상속받아 사용합니다.

  • 종류
    • HashSet: 내부적으로 HashMap을 사용하며, Set Interface를 상속합니다.
    • TreeSet: 내부적으로 TreeMap을 사용하며, NavigableSet interface를 상속합니다. NavigableSet는 SortedSet interface를 상속하기 때문에 정렬을 할 수 있습니다.
    • LinkedHashSet: 내부적으로 LinkedHashMap 사용하며, HashSet Class를 상속합니다.

차이점

  • HashSet: 순서없이 데이터가 삽입된다.
  • TreeSet : 데이터 삽입 시, 기본 값으로 오름차순 정렬이 된다.
  • LinkedHashSet: 삽입된 순서대로 데이터가 저장된다.

성능 비교

HashSet, TreeSet, LinkedHashSet 클래스를 비교해봅시다. 실제로 데이터가 중복, 정렬이 안되는지 그리고 삽입과 삭제에 대한 성능 테스트를 실행해보았습니다.
아래는 성능 테스트에 사용한 코드입니다. 순서는 중복 테스트, 정렬 테스트, 삽입 테스트, 삭제 테스트로 진행했습니다. 성능 테스트 깃헙 링크

public class Main {
	public static void main(String[] args) {
		System.out.println("--- duplicate test");
		duplicateTest();
		System.out.println("--- sort test");
		sortTest();
		System.out.println("--- add performance test");
		performanceTest();
	}

	private static void sortTest() {
		Set<Integer> hashSet = new HashSet<>();
		Set<Integer> treeSet = new TreeSet<>();
		Set<Integer> linkedHashSet = new LinkedHashSet<>();
		int[] arr = {10, 20, 30, 40, 80, 50, 60, 70};

		for (int i : arr) {
			hashSet.add(i);
		}
		System.out.println("[hashSet]" + hashSet.toString());

		for (int i : arr) {
			treeSet.add(i);
		}
		System.out.println("[treeSet]" + treeSet.toString());

		for (int i : arr) {
			linkedHashSet.add(i);
		}
		System.out.println("[linkedHashSet]" + linkedHashSet.toString());
	}

	private static void duplicateTest() {
		HashSet<Integer> hashSet = new HashSet<>();

		int[] arr = {10, 10, 20, 20, 30, 30};

		for (int i : arr) {
			hashSet.add(i);
		}

		System.out.println(hashSet.toString());
	}

	private static void performanceTest() {
		Set<String> hashSet = new HashSet<>();
		Set<String> treeSet = new TreeSet<>();
		Set<String> linkedHashSet = new LinkedHashSet<>();

		// 1,000,000개의 데이터 삽입
		String data = "DATA";
		int size = 1000000;

		StopWatch hashSetWatch = new StopWatch("add hashSet");
		hashSetWatch.start();
		for (int i = 0; i < size; i++) {
			hashSet.add(data + i);
		}
		hashSetWatch.stopAndPrint();

		StopWatch treeSetWatch = new StopWatch("add treeSet");
		treeSetWatch.start();
		for (int i = 0; i < size; i++) {
			treeSet.add(data + i);
		}
		treeSetWatch.stopAndPrint();

		StopWatch linkedHashSetWatch = new StopWatch("add linkedHashSet");
		linkedHashSetWatch.start();
		for (int i = 0; i < size; i++) {
			linkedHashSet.add(data + i);
		}
		linkedHashSetWatch.stopAndPrint();

		System.out.println("--- revmoe performance test");
		StopWatch hashSetWatch2 = new StopWatch("remove hashSet");
		hashSetWatch2.start();
		for (int i = 0; i < hashSet.size(); i++) {
			hashSet.remove(data + i);
		}
		hashSetWatch2.stopAndPrint();

		StopWatch treeSetWatch2 = new StopWatch("remove treeSet");
		treeSetWatch2.start();
		for (int i = 0; i < hashSet.size(); i++) {
			treeSet.remove(data + i);
		}
		treeSetWatch2.stopAndPrint();

		StopWatch linkedHashSetWatch2 = new StopWatch("remove linkedHashSet");
		linkedHashSetWatch2.start();
		for (int i = 0; i < hashSet.size(); i++) {
			treeSet.remove(data + i);
		}
		linkedHashSetWatch2.stopAndPrint();
	}
}

테스트 결과

{10, 10, 20, 20, 30, 30} 배열을 삽입해보았는데, Set은 중복을 보장하지 않는 것을 확인 할 수 있습니다.
예제2

순서 테스트는 {10, 20, 30, 40, 80, 50, 60, 70} 데이터를 이용하였고 중간에 ‘40’ 다음에 ‘80’을 넣어 실제로 정렬이 이루어지는 지 확인했습니다. 아래 이미지와 같이 HashSet은 순서를 보장하지 않아 뒤죽박죽이고 TreeSet은 순서를 보장하고 LinkedHashSet는 삽입된 순서대로 데이터가 저장된 것을 확인 할 수 있습니다.
예제3

삽입과 삭제 테스트는 100만개의 데이터를 이용하여 테스트 했으며, HashSet > LinkedHashSet > TreeSet 순서대로 속도가 나왔습니다. 결과는 아래 이미지와 같습니다.
예제4

결론

  • HashSet: 중복과 순서에 관계없는 데이터 집합이 필요하면 선택
  • TreeSet: 중복은 보장하지 않지만 순서 보장이 필요한 데이터 집합이 필요하면 선택
  • LinkedHashSet: 삽입된 순서대로 데이터 정렬이 필요하면 선택

Categories:

Updated:

Leave a comment