데이터를 해쉬 테이블에 담는 클래스로 순서 없이 저장
red-black 이라는 트리에 데이터를 저장,
값에 따라서 순서가 정해짐
, 데이터를 담으면서 동시에정렬
하기 때문에 HashSet보다 성능상 느림
해쉬 테이블에 담는데
저장된 순서에 따라 순서가 결정됨
메서드가 수행될때마다 1000번을 for문으로 돌면서 데이터를 추가하는 테스트를 진행
테스트 코드
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import javax.annotation.processing.Generated;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.MICROSECONDS) //밀리세컨드 단위로 출력
@Fork(value = 2, jvmArgs= {"-Xms4G", "-Xmx4G"}) //측정을 한번만 하는것은 벤치마크의 신뢰성에 문제가 있을 수 있다.
// 특정 시점에 시스템이 다른 이유로 갑자기 느려지거나 하는 상황이 있을 수 있기 때문에 최대한 외부 변수의 영향을 배제하기 위해 측정을 2회 실시한다.
//그리고 힙 영역의 공간 부족으로 인한 gc 오버헤드를 최소화 하기 위해 힙 영역의 크기를 4GB 로 설정한다.
public class SetAdd {
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(SetAdd.class.getSimpleName())
.forks(2)
.build();
new Runner(opt).run();
}
int LOOP_COUNT = 1000;
Set<String> set;
String data = "abcdefghigklmnopqrstuvwxyz";
@Benchmark
public void addHashSet(){
set = new HashSet<String>();
for (int loop=0; loop<LOOP_COUNT; loop++){
set.add(data+loop);
}
}
@Benchmark
public void addTreeSet(){
set = new TreeSet<String>();
for (int loop=0; loop<LOOP_COUNT; loop++){
set.add(data+loop);
}
}
@Benchmark
public void addLinkedHashSet(){
set = new LinkedHashSet<>();
for (int loop=0; loop<LOOP_COUNT; loop++){
set.add(data+loop);
}
}
}
테스트 결과
데이터 추가 테스트는
HashSet
과LinkedHashSet
이 성능이 비슷하다.
TreeSet
은 데이터 추가시 동시에 정렬로 인해서 성능상 느리다.
부가 테스트
HashSet에서 미리 사이즈를 알고 있다면?
@Benchmark
public void addHashSetKnowSize(){
set = new HashSet<String>(LOOP_COUNT);
for (int loop=0; loop<LOOP_COUNT; loop++){
set.add(data+loop);
}
}
미세한 차이지만 사이즈를 알고있다면 데이터를 추가하는 속도가 미세하게 빠른것을 확인할 수 있다.
LOOP_COUNT 까지의 데이터를 각 Set에 넣어두고 랜덤으로 생성한 데이터를 Set에서 찾는 테스트
테스트코드
package test;
import org.apache.commons.math3.genetics.RandomKey;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.*;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.MICROSECONDS) //밀리세컨드 단위로 출력
public class SetIterate {
int LOOP_COUNT = 1000;
Set<String> hashSet;
Set<String> treeSet;
Set<String> linkedHashSet;
String data = "abcdefghigklmnopqrstuvwxyz";
String[] keys;
String result = null;
@Setup(Level.Trial)
public void setUp(){
hashSet=new HashSet<>();
treeSet=new TreeSet<>();
linkedHashSet=new LinkedHashSet<>();
for (int loop=0; loop<LOOP_COUNT; loop++){
String tempData=data+loop;
hashSet.add(tempData);
treeSet.add(tempData);
linkedHashSet.add(tempData);
}
if(keys==null || keys.length!=LOOP_COUNT){
keys= RandomKeyUtil.generateRandomSetKeysSwap(hashSet);
}
}
@Benchmark
public void containHashSet(){
for(String key:keys){
hashSet.contains(key);
}
}
@Benchmark
public void containTreeSet(){
for(String key:keys){
treeSet.contains(key);
}
}
@Benchmark
public void containLikedSet(){
for(String key:keys){
linkedHashSet.contains(key);
}
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(SetIterate.class.getSimpleName())
.forks(2)
.warmupIterations(5)
.measurementIterations(5)
.build();
new Runner(opt).run();
}
}
public class RandomKeyUtil {
public static String[] generateRandomSetKeysSwap(Set<String> set){
int size = set.size();
String[] result = new String[size];
Random random = new Random();
int maxNumber = size;
Iterator<String> iterator=set.iterator();
int resultPost=0;
while (iterator.hasNext()){
result[resultPost++]=iterator.next();
}
for(int loop=0; loop<size; loop++){
int randomNum1 = random.nextInt(maxNumber);
int randomNum2 = random.nextInt(maxNumber);
String temp=result[randomNum2];
result[randomNum2]=result[randomNum1];
result[randomNum1]=temp;
}
return result;
}
}
TreeSet
이 가장 느렸으며나머지 두개는 비슷하게 빨랐다.
데이터를 순서에 따라서 탐색하는 작업에는
TreeSet
이 좋으나그럴 필요가 없을땐
HashSet
또는LinkedHashSet
을 사용하자
+추가)
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable
TreeSet은
NavigableSet
인터페이스를 구현하고 있으며 해당 인터페이스는 특정값보다 큰값/작은값,가장 큰값/가장 작은값 등을 추출하는 메서드를 지원한다.
따라서 순서에 따라 탐색하는 작업이 필요할땐 TreeSet을 사용하는것이 좋다.