Set

  • Java
  • 2021년 6월 1일

Set은 자바의 Collection중에 객체를 중복하지 않고 하나만 저장하는 자료구조로 List와 다르게 저장순서(index)를 따로 저장하지 않기 때문에 이를 통해 접근할 수 없다.

Set interface 제공 메서드

메서드리턴 값설명
add(E e)boolean객체 추가성공하면 true
addAll(Collection c)boolean컬렉션을 추가하면 데이터들을 Set에 맞게 저장
remove(Object o)boolean객체 삭제
contains(Object o)boolean객체가 포함되었는지 여부
eqauls(Object o)boolean같은지 비교
clear()voidSet 초기화
iterator()Iteratorset 요소 접근하기 위한 Iterator객체 반환



1. HashSet

Set을 구현한 구현체중에 하나로 Key를 Hash와 하여 저장하는 Set이다.

//선언
Set<Integer> set = new HashSet<>();
//add
System.out.println(set.add(1)); //true
System.out.println(set.add(1)); //false
System.out.println(set.add(0)); //true
System.out.println(set.add(-1)); //true
System.out.println(set.add(2)); //true

//iterator를 이용한 모든 key 접근
Iterator<Integer> it = set.iterator();
while(it.hasNext()){
    System.out.println(it.next()); //0, -1, 1, 2
}

//remove
System.out.println(set.remove(1));    //true

//contains
System.out.println(set.contains(1));  //false

it = set.iterator();
while(it.hasNext()){
    System.out.println(it.next()); //2
}

HashSet은 Set에서 제공하는 메서드외에는 별도로 제공하는 메서드가 없고 내부적으로는 HashMap으로 동작하기 때문에 접근속도가 빠르다. 그리고 별도의 index를 가지지 않기 때문에 모든 데이터를 순회하고 싶을때는 Iterator를 이용해서 접근하면 된다.


//HashSet 내부
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();

public HashSet() {
    map = new HashMap<>();
}

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

위는 HashSet의 내부를 일부 가져온 것인데 살펴보면 내부 필드로 HashMap을 가지고 있어 이를 이용(합성)해서 HashSet을 구현하고 있고 add와 같은 메서드도 Map의 put메서드를 이용해서 데이터를 추가하는 것을 볼 수 있다.

여기서 한가지 더 유심히 볼 부분이 PRESENT라는 객체인데 Set은 key만을 저장하는 자료구조이기 때문에 value는 어떤 객체가 오든 상관이없다. 그래서 HashSet을 만든 개발자는 어떤 HashSet에서든 모든 데이터는 value값을 동일한 value값을 가르키도록 static으로 PRESENT라는 객체를 생성해 메모리를 절약하는식으로 구성을 하였다.



2. LinkedHashSet

HashSet과 거의 동일하지만 데이터의 입력된 순서대로 저장을하고 관리를 한다. 기본적으로는 HashSet보다는 성능이 조금 뒤쳐지지만, HashMap의 capacity가 커지는 경우에는 HashSet보다는 성능이 좋다.

Set<Integer> set = new LinkedHashSet<>();
System.out.println(set.add(1)); //true
System.out.println(set.add(0)); //true
System.out.println(set.add(-1)); //true
System.out.println(set.add(2)); //true

Iterator<Integer> it = set.iterator();
while(it.hasNext()){
    System.out.println(it.next()); //1, 0, -1, 2
}

LinkedHashSet도 Set의 메서드만을 갖고 있고, HashSet과 비교해해보면 데이터를 추가한 순서대로 출력하는 것을 볼 수 있다.



3. TreeSet

TreeSet은 정렬된 상태의 Set을 사용하고 싶을때 사용할 수 있는 구현체로 SortedSet을 상속받은 NavigableSet을 구현한 구현체이다.

◾ 제공 메서드

메서드리턴 값설명
add(E e)boolean객체 추가성공하면 true
addAll(Collection c)boolean컬렉션을 추가하면 데이터들을 Set에 맞게 저장
remove(Object o)boolean객체 삭제
contains(Object o)boolean객체가 포함되었는지 여부
eqauls(Object o)boolean같은지 비교
clear()voidSet 초기화
iterator()Iteratorset 요소 접근하기 위한 Iterator객체 반환
descendingIterator()Iterator현재 정렬 순서의 반대로의 Iterator객체 반환
ceiling(E e)E객체보다 크거나 같은 객체중 가장 가까운 객체 return
higher(E e)E객체보다 큰 객체중 가장 가까운 객체 return
floor(E e)E객체보다 작거나 같은 객체중 가장 가까운 객체 return
lower(E e)E객체보다 작은 객체중 가장 가까운 객체 return
first()E첫번째 객체 return
last()E마지막 객체 return
headSet(E e)SortedSet객체보다 큰 객체들만 모아 Set return
headSet(E,boolean)NavigableSetheadSet()에서 객체E를 포함할지 결정해서 return
subSet(E from,E to)SorteSetfrom는 포함하고 to는 포함하지 않고 to까지의 객체 Set return
subSet(E from,boolean, E to,boolean)NavigableSetfrom부터 to 각각 같은거 포함할지 결정해서 Set return
tailSet(E e)SortedSet객체보다 작거나 같은 객체Set return
tailSet(E,boolean)NavigableSet객체를 포함할지 결정해 Set return
pollFirst()E첫번째 객체 삭제하면서 return
pollLast()E마지막 객체 삭제하면서 return

◾ 사용 예

TreeSet<Integer> set = new TreeSet<>(Arrays.asList(4,2,3,-1,0,8,4,2,7,10));
System.out.println(set);    //[-1, 0, 2, 3, 4, 7, 8, 10]

//근접한 요소 접근
System.out.println(set.ceiling(4));   //4
System.out.println(set.higher(4));    //7
System.out.println(set.floor(2));     //2
System.out.println(set.floor(2));     //0

System.out.println();

//부분집합 추출
System.out.println(set.headSet(3));             //[-1, 0, 2]
System.out.println(set.headSet(3,true));        //[-1, 0, 2, 3]
System.out.println(set.subSet(3,8));            //[3, 4, 7]
System.out.println(set.subSet(3,false,8,true)); //[4, 7, 8]
System.out.println(set.tailSet(4));             //[4, 7, 8, 10]
System.out.println(set.tailSet(4,false));       //[7, 8, 10]

System.out.println();

//Iterator
Iterator it = set.iterator();
while(it.hasNext()){
    System.out.print(it.next() + " ");    //-1 0 2 3 4 7 8 10
}
System.out.println();

//역순 Iterator
it = set.descendingIterator();          //10 8 7 4 3 2 0 -1
while(it.hasNext()){
    System.out.print(it.next() + " ");
}
System.out.println();


//내림차순 TreeSet 선언
TreeSet<Integer> descSet = new TreeSet<>(Collections.reverseOrder());
descSet.addAll(Arrays.asList(4,2,3,-1,0,8,4,2,7,10));
System.out.println(descSet);    //[10, 8, 7, 4, 3, 2, 0, -1]

TreeSet은 이름에서 유추할 수 있듯이 내부적으로 Tree로 구성되어있는 Set이다. 그렇기 때문에 특정 원소를 기준으로 다음,이전값이나 부분집합을 구할 수 있는 것인데 Tree는 데이터에따라 잘못하면 편향트리가 되어 접근하는데 O(N)이 소모되기 때문에 Balancing을 해주어야한다. 그래서 TreeSet도 내부를 봐보면 HashSet이 HashMap을 이용해서 합성한 것 처럼 TreeSet도 TreeMap을 이용해서 합성해 구현되어 있다.


//TreeSet 내부구조 중 일부

private transient NavigableMap<E,Object> m;
private static final Object PRESENT = new Object();

public TreeSet() {
  this(new TreeMap<>());
}

public boolean add(E e) {
  return m.put(e, PRESENT)==null;
}

TreeMap은 SortedMap의 구현체로 내부적으로 Red Black Tree로 구현되어 있어 balancing을 수행한다. 이런 이유로 HashSet은 O(1)만에 데이터를 접근할 수 있지만 TreeSet은 접근도 O(logN), 삽입/수정도 balancing수행으로 O(logN)만큼의 시간이 소요된다.

특정한 목적이 있지 않는이상 HashSet을 이용하는 것이 performance에 좋다.





Reference

https://docs.oracle.com/javase/8/docs/api/

Tags :

Related Posts

Clean Code

Clean Code

  • Books
  • 2021년 1월 22일

1장. 깨끗한 코드 코드 품질을 측정하는 유일한 척도 = 분당 내지르는 &lsquo;WTF!&rsquo; 횟수 코드는 요구사항을 상세히 표현하는 수단 ( 기계가 실행할 정도로 상세하게 요구사항을 명시하는 작업 = 프로그래밍 ) 작성자가 아닌 사람도 읽고 고치기 쉽고 단순하고 직접적이다 중복을 피하고 한 기능만 수행하게 작제 추상화하기 프로그래밍은 코드를 읽는 시간 대 짜는 시간 비율이 9:1 잘 짠 코드도 시간이 지나면 레거시가 되니 조금씩 코드를 정리/개선하자 2장. 의미있는 이름 클래스/메서드 이름 클래스 : 명사, 명사구가 적합 메서드 : 동사, 동사구가 적합 의도를 명확하게 밝히자 ( 코드의 맥락이 코드자체에 명시적으로 드러내자) 잘못된 정보를 피하자 약어를 함부로 사용하지말자 0/O, l/1등과 같이 서로 헷갈리게 하는 변수명을 짓지말자 의미있게 구분하자 단순히 컴파일러나 인터프리터를 통과할 목적의 네이밍하지 말자 a1,a2와 같이 연속된 숫자나, 불용어를 지양하자 ex getAccount(),getAccounts(), getAccountInfo() 와 같은 메서드가 있다면 새로운 프로젝트 참가자는 메서드를 구분하기 힘들다 발음하기 쉬운 이름을 사용하자 검색하기 쉬운 이름을 사용하자 간단한 메서드에서 로컬 변수는 한 문자를 사용하더라도 상수나 대부분의 변수는 긴 이름이름이 검색하기에도 더 편하다 요즘 좋은 IDE들은 몇자 타이핑 안해도 자동 추천해주니 검색성능 면에서는 긴이름이 더 좋다 인코딩을 피하자 마찬가지로 요즘 IDE들은 코드를 컴파일하지 않고도 타입 오류를 감지할 정도로 똑똑하기 때문에 헝가리안 표기법을 지양 하자 한 개념에 한 단어를 사용하자 fetch/get/retrieve 나 controller/manager/driver와 같이 비슷한 의미의 단어를 혼용해서 사용하는 것을 지양하자 add/insert/append 와 같이 추가하는 메서드라고 하더라고 맥락이 다르면 다른 단어를 사용하자 ( 의도 를 명확하게 밝히는 것이 중요! ) 3장. 함수 가능한 한 작고 한가지 기능만 수행하도록 작성하자....

Read More
Spring boot와 EK스택 연결하기

Spring boot와 EK스택 연결하기

현재 진행하고있는 spring boot 프로젝트에 쿼리 통계를 위해 ElasticSearch와 Kibana를 도입하기로 했고 이에 맞게 로깅방법이나 세팅 방법을 기록하기 위해 글을 작성하게 됬다. diagram 구성은 다음과 같은데 서버로 EC2 프리티어를 사용하고 있어 사양이 안좋기 때문에 서버 내부에 logstash없이 filebeat를 이용하여 로그를 전송하기로 했다. ELK스택으로는 Elastic Cloud 프리티어를 이용해 빠르게 구축하는 것을 테스트해볼 생각이다....

Read More
[APSP] Floyd Warshall 알고리즘

[APSP] Floyd Warshall 알고리즘

벨만-포드 알고리즘과 다익스트라 알고리즘과 달리 모든 최단 경로를 구하는 알고리즘이다. (물론 두 알고리즘도 모든 정점에대해 수행하면 모든 최단 경로를 구할 수 있다.) 1. 특징 음의 가중치 허용 optimal substructure 개념 이용 배열을 이용하여 구현 밀집그래프에서 모든 edge간 경로 구할때 적합 2. Pesudo Code 3. 구현 방법 그래프 edge가 주어졌을때, edge들의 정보를 이용하여 각 edge간 거리 정보를 저장할 distance 2차원 행렬과 경로를 구하기 위해 이전 노드를 저장할 previous 2차원 행렬 생성 distance 행렬은 Infinity로 previous 행렬은 NIL(-1)로 초기화 그래프 G의 edge들의 가중치의 정보를 이용해 distance행렬을 초기화하고 자기의 거리는 0으로 초기화 3중 반복문을 이용하여, 현재까지 계산된 i - j까지의 경로 값보다 사이에 k를 경유하는 경로 값이 더 작다면 값을 바꾸기 4. 시간복잡도 매번 모든 노드들의 조합에 대해서 현재까지의 최단 경로를 구하고 총 |V-1| 번 반복하기 때문에 O(|V|^3) 의 시간복잡도를 갖는다....

Read More