[Java] 자바 101 기초 강의 #12 ( Set형 )
- Lecture/Java 강의
- 2022. 4. 27.
자바의 Set형은 List와 유사한 형제같은 데이터형입니다.
그림 1에서 보다시피 Set과 List는 Collection이라는 interface의 하위 interface입니다. 그러다보니 list와 set은 사용법이 유사합니다.
List와 Set의 차이점
List와 Set의 차이점은 List는 데이터를 저장하고 출력하는 것에 포커스를 둬서 동일한 데이터도 저장할 수 있다면, Set의 경우 데이터를 탐색하고, 중복되는 데이터를 제거하는 등의 기능이 있습니다.
리스트 예시
public static void main(String[] args) {
String[] arrs = {"블랙핑크","BTS","블랙핑크"};
List<String> list = new ArrayList<>(Arrays.asList(arrs));
System.out.println("중복된 것도 나오는 리스트 -> " + list);
}
# 중복된 것도 나오는 리스트 -> [블랙핑크, BTS, 블랙핑크]
리스트는 위의 경우 출력 예시처럼 블랙핑크라는 동일한 값을 출력합니다.
Set 예시
public static void main(String[] args) {
String[] arrs = {"블랙핑크","BTS","블랙핑크"};
Set<String> setData = new HashSet<>(Arrays.asList(arrs));
System.out.println("중복된 것은 제거하는 셋 -> " + setData);
}
# 중복된 것은 제거하는 셋 -> [BTS, 블랙핑크]
하지만 Set은 기본적으로 중복을 제거하게 됩니다.
Set 종류
HashSet
HashSet은 Set에서 가장 많이 사용하며, 내부적으로 해시 알고리즘(Hash algorithm)을 사용하기 때문에 검색 속도가 빠릅니다. 이는 Map의 구현 객체중 HashMap과도 동일합니다.
즉, HashSet의 사용 목적은 중복된 데이터를 저장하지 않고, 데이터를 빠르게 탐색하기 위함입니다.
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
for(int i = 0; i < 100000; i++) {
list1.add("data" + i);
list2.add("data" + i);
}
Collections.shuffle(list2);
System.out.println("list1 n10 ->" + list1.subList(0, 10));
System.out.println("list2 n10 ->" + list2.subList(0, 10));
}
우선 HashSet의 비교를 위해서, 리스트1,2 데이터를 다음과 같이 세팅하였고, list1을 loop를 돌려서 list2에 데이터를 찾는 시간을 확인해보도록 하겠습니다.
리스트 탐색 속도
public static void scanList(List<String> list1, List<String> list2) {
long startTime = System.currentTimeMillis();
int scanCount = 0;
for(String str : list1) {
if(list2.contains(str)) {
scanCount++;
}
}
System.out.println("scan list -> (" + scanCount + "), elapsed -> " + (System.currentTimeMillis()-startTime) + "(ms)");
}
# scan list -> (100000), elapsed -> 91816(ms)
10만개의 리스트를 탐색하는데 걸린 시간은 무려 91초가 걸렸습니다. list의 경우 index가 안되어 있기 때문에 데이터를 직접 모두 뒤져야 하기 때문입니다.
그럼 탐색 대상인 list2를 HashSet으로 변경해보겠습니다.
HashSet 탐색 속도
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
Set<String> setData = new HashSet<>();
for(int i = 0; i < 100000; i++) {
list1.add("data" + i);
setData.add("data" + i);
}
scanList(list1, setData);
}
public static void scanList(List<String> list1, Set<String> setData) {
long startTime = System.currentTimeMillis();
int scanCount = 0;
for(String str : list1) {
if(setData.contains(str)) {
scanCount++;
}
}
System.out.println("scan list -> (" + scanCount + "), elapsed -> " + (System.currentTimeMillis()-startTime) + "(ms)");
}
# scan list -> (100000), elapsed -> 14(ms)
HashSet으로 변경을 하니, 14 밀리세컨드로 6558배 빨라졌습니다. 이 속도는 데이터가 많아지면 많아질수록 차이는 더욱 커집니다.
TreeSet
TreeSet은 HashSet의 강점을 어느정도 가져간 상태에서 데이터를 정렬하는 기능을 추가하였습니다.
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
Set<String> setData = new TreeSet<>();
for(int i = 0; i < 100000; i++) {
list1.add("data" + i);
setData.add("data" + i);
}
scanList(list1, setData);
}
public static void scanList(List<String> list1, Set<String> setData) {
long startTime = System.currentTimeMillis();
int scanCount = 0;
for(String str : list1) {
if(setData.contains(str)) {
scanCount++;
}
}
System.out.println("scan list -> (" + scanCount + "), elapsed -> " + (System.currentTimeMillis()-startTime) + "(ms)");
}
# scan list -> (100000), elapsed -> 24(ms)
HashSet에서 TreeSet으로 변경 후 위와 같은 예제를 실행하니, 14ms에서 24ms 정도로 약간 느려진 것을 확인할 수 있습니다. 하지만 list에 비해서 3825배 더 빠른 속도를 보여주고 있습니다.
public static void main(String[] args) {
Set<String> hashSetData = new HashSet<>();
Set<String> treeSetData = new TreeSet<>();
// 100만
long startTime = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
hashSetData.add("data" + i);
}
System.out.println("hash set elapsed -> " + (System.currentTimeMillis()-startTime));
startTime = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
treeSetData.add("data" + i);
}
System.out.println("tree set elapsed -> " + (System.currentTimeMillis()-startTime));
int i = 0;
for(String hashStr : hashSetData) {
System.out.println(hashStr);
if(++i >= 10) break;
}
i = 0;
for(String treeStr : treeSetData) {
System.out.println(treeStr);
if(++i >= 10) break;
}
}
hashset, treeset 실행결과
hash set elapsed -> 121
tree set elapsed -> 259
data581470
data581471
data581472
data581473
data581474
data581475
data581476
data581477
data581478
data581479
data0
data1
data10
data100
data1000
data10000
data100000
data100001
data100002
data100003
이번엔 10만건이 아니라 100만건으로 테스트를 하였고, 10건의 데이터를 순서대로 가져와봤습니다. HashSet의 경우 데이터의 순서를 우리가 알 수 없지만, TreeSet의 경우 데이터가 오름차순 형태로 저장이 된 것을 확인할 수 있습니다. 즉, 아래와 같이 오름차순 형태로 데이터를 확인해야 된다면 TreeSet이 유리할 것입니다.
LinkedHashSet
HashSet이 순서대로 저장이 되지 않는 것이 문제라면 사실 LinkedHashSet을 사용하면 됩니다. HashSet인 상태에서 데이터를 순서대로 저장을 한 형태라 보시면 됩니다.
public static void main(String[] args) {
Set<String> hashSetData = new HashSet<>();
Set<String> treeSetData = new TreeSet<>();
Set<String> linkedHashSetData = new LinkedHashSet<>();
// 100만
long startTime = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
hashSetData.add("data" + i);
}
System.out.println("hash set elapsed -> " + (System.currentTimeMillis()-startTime));
startTime = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
treeSetData.add("data" + i);
}
System.out.println("tree set elapsed -> " + (System.currentTimeMillis()-startTime));
startTime = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
linkedHashSetData.add("data" + i);
}
System.out.println("linked hash set elapsed -> " + (System.currentTimeMillis()-startTime));
System.out.println("hash set data.....................................");
int i = 0;
for(String hashStr : hashSetData) {
System.out.println(hashStr);
if(++i >= 5) break;
}
System.out.println("tree set data.....................................");
i = 0;
for(String treeStr : treeSetData) {
System.out.println(treeStr);
if(++i >= 5) break;
}
System.out.println("linked hash set data.....................................");
i = 0;
for(String linkedStr : linkedHashSetData) {
System.out.println(linkedStr);
if(++i >= 5) break;
}
}
hashset, treeset, linkedhashset 실행결과
hash set elapsed -> 124
tree set elapsed -> 243
linked hash set elapsed -> 126
hash set data.....................................
data581470
data581471
data581472
data581473
data581474
tree set data.....................................
data0
data1
data10
data100
data1000
linked hash set data.....................................
data0
data1
data2
data3
data4
TreeSet의 경우 데이터의 값을 기준으로 오름차순 하지만, linked hash set의 경우 데이터가 저장된 순서 그대로 가져올 수 있습니다.
우리가 값을 오름차순하여 가져와야 되는 경우가 사실 많지 않기 때문에 데이터의 순서가 문제가 없다면, HashSet을 쓰거나 데이터의 순서가 중요하다면 LinkedHashSet을 사용하는 것이 일반적입니다.
Set 메소드 [1]
메소드 | 설명 |
boolean add(E) |
지정된 요소가 아직 없는 경우 이 집합에 추가합니다 (선택)
|
boolean addAll(Collection) |
지정된 컬렉션의 모든 요소가 아직 없는 경우 이 집합에 추가합니다 (선택)
|
void clear() |
집합에서 모든 요소를 제거합니다 (선택)
|
boolean contains(Object) |
집합에 지정된 요소가 포함되어 있으면 true를 반환 합니다.
|
boolean containsAll(Object) |
집합에 지정된 컬렉션의 모든 요소가 포함되어 있으면 true를 반환 합니다.
|
boolean equals(Object) |
지정된 개체를 이 집합과 동일한지 비교합니다.
|
int hashCode() |
집합에 대한 해시 코드 값을 반환합니다.
|
boolean isEmpty() |
집합에 요소가 없으면 true를 반환 합니다.
|
Iterator<E> iterator() |
세트의 요소에 대한 반복자를 반환합니다.
|
boolean remove(Object) |
집합에서 지정된 요소가 있는 경우 제거합니다 (선택)
|
boolean removeAll(Collection) |
지정된 컬렉션에 포함된 모든 요소를 집합에서 제거합니다 (선택)
|
boolean retainAll(Collection) | 지정된 컬렉션에 포함된 집합의 요소만 유지합니다 (선택) |
int size() | 이 집합의 개수를 반환합니다. |
default Spliterator<E> spliterator() |
Spliterator 세트의 요소를 통해 생성합니다 .
|
Object[] toArrays() |
세트의 모든 요소를 포함하는 배열을 반환합니다.
|
<T> T[] toArray(T[]) | 세트의 모든 요소를 포함하는 배열을 반환합니다. 반환된 배열의 런타임 유형은 지정된 배열의 런타임 유형입니다. |
연관포스팅
[Java] 자바 101 기초 강의 #11 ( List형 )
References
[1] https://docs.oracle.com/javase/8/docs/api/java/util/Set.html
'Lecture > Java 강의' 카테고리의 다른 글
[Java] 자바 101 강의 #14 ( 반복문 for형 ) (0) | 2022.05.02 |
---|---|
[Java] 자바 101 강의 #13 ( Map형 ) (0) | 2022.05.01 |
[Java] 자바 101 기초 강의 #11 ( List형 ) (0) | 2022.04.26 |
[Java] 자바 101 강의 #10 ( Switch문 사용하기 ) (0) | 2022.04.25 |
[Java] 자바 101 강의 #9 ( IF 조건문, 성적 등급 프로그램 ) (0) | 2022.04.22 |