답변:
HashSet은 TreeSet보다 훨씬 빠르지 만 (추가, 제거 및 포함과 같은 대부분의 작업에서 상수 시간 대 로그 시간) TreeSet과 같은 순서 보장은 제공하지 않습니다.
SortedSet
)first()
, last()
, headSet()
, 및 tailSet()
등HashSet
및 TreeSet
. 그러나 연결된 목록을 통해 실행되는 해시 테이블로 구현되지만 TreeSet에 의해 보장 된 정렬 된 순회와 동일하지 않은 삽입 순서 반복을 제공합니다 .따라서 사용법의 선택은 전적으로 귀하의 요구에 달려 있지만 정렬 된 컬렉션이 필요하더라도 여전히 HashSet을 선호하여 Set을 만든 다음 TreeSet으로 변환해야한다고 생각합니다.
SortedSet<String> s = new TreeSet<String>(hashSet);
아직 언급되지 않은 한 가지 장점 TreeSet
은 "지역성 (locality)"이 더 크다는 것인데, 이는 (1) 두 항목이 순서대로 근처에 있으면 TreeSet
데이터 구조에서 서로 가까이 배치되어 메모리에 배치된다. 그리고 (2)이 배치는 지역성의 원칙을 이용하는데, 이는 유사한 데이터가 종종 유사한 주파수를 가진 응용 프로그램에 의해 액세스된다고 말합니다.
이것은 대조적으로 HashSet
키와 상관없이 메모리 전체에 항목을 분산시키는와 입니다.
하드 드라이브에서 읽는 대기 시간 비용이 캐시 나 RAM에서 읽는 시간의 수천 배인 경우, 데이터가 실제로 로컬로 액세스되는 TreeSet
경우 훨씬 더 나은 선택이 될 수 있습니다.
TreeSet
/ 구현은 지역적으로 TreeMap
최적화되지 않았습니다. 레드-블랙 트리를 나타 내기 위해 차수 4의 b- 트리를 사용하여 지역 성과 캐시 성능을 향상시킬 수는 있지만 구현 방식이 아닙니다. 대신 각 노드는 TreeMap.Entry 의 JDK 8 소스 코드 에서 알 수 있듯이 자체 키, 자체 값, 상위 및 왼쪽 및 오른쪽 하위 노드에 대한 포인터를 저장합니다 .
HashSet
요소에 액세스하려면 O (1)이므로 확실히 중요합니다. 그러나 세트에서 객체의 순서를 유지하는 것은 불가능합니다.
TreeSet
순서를 유지하는 것이 중요합니다 (삽입 순서가 아닌 값으로). 그러나 앞에서 언급했듯이 기본 작업의 경우 요소에 액세스하는 데 시간이 오래 걸리는 주문을 거래하고 있습니다.
에 대한 javadocs에서TreeSet
:
이 구현은 기본 작업 (
add
,remove
및contains
)에 대해 보장 된 log (n) 시간 비용을 제공합니다 .
1. HashSet은 null 객체를 허용합니다.
2. TreeSet은 null 객체를 허용하지 않습니다. null 값을 추가하려고하면 NullPointerException이 발생합니다.
3.HashSet은 TreeSet보다 훨씬 빠릅니다.
예 :
TreeSet<String> ts = new TreeSet<String>();
ts.add(null); // throws NullPointerException
HashSet<String> hs = new HashSet<String>();
hs.add(null); // runs fine
null
어떤 식 으로든 세트에 추가해서는 안됩니다 .
TreeSet<String> badassTreeSet = new TreeSet<String>(new Comparator<String>() { public int compare(String string1, String string2) { if (string1 == null) { return (string2 == null) ? 0 : -1; } else if (string2 == null) { return 1; } else { return string1.compareTo(string2); } } }); badassTreeSet.add("tree"); badassTreeSet.add("asdf"); badassTreeSet.add(null); badassTreeSet.add(null); badassTreeSet.add("set"); badassTreeSet.add("tree"); System.out.println(badassTreeSet);
@shevchyk의 멋진 시각적 답변 을 바탕으로 여기를 사용합니다.
╔══════════════╦═════════════════════╦═══════════════════╦═════════════════════╗
║ Property ║ HashSet ║ TreeSet ║ LinkedHashSet ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║ ║ no guarantee order ║ sorted according ║ ║
║ Order ║ will remain constant║ to the natural ║ insertion-order ║
║ ║ over time ║ ordering ║ ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║ Add/remove ║ O(1) ║ O(log(n)) ║ O(1) ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║ ║ ║ NavigableSet ║ ║
║ Interfaces ║ Set ║ Set ║ Set ║
║ ║ ║ SortedSet ║ ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║ ║ ║ not allowed ║ ║
║ Null values ║ allowed ║ 1st element only ║ allowed ║
║ ║ ║ in Java 7 ║ ║
╠══════════════╬═════════════════════╩═══════════════════╩═════════════════════╣
║ ║ Fail-fast behavior of an iterator cannot be guaranteed ║
║ Fail-fast ║ impossible to make any hard guarantees in the presence of ║
║ behavior ║ unsynchronized concurrent modification ║
╠══════════════╬═══════════════════════════════════════════════════════════════╣
║ Is ║ ║
║ synchronized ║ implementation is not synchronized ║
╚══════════════╩═══════════════════════════════════════════════════════════════╝
가장 많이 사용하는 이유 HashSet
는 연산이 O (log n) 대신 (평균) O (1)이기 때문입니다. 세트에 표준 항목이 포함되어 있으면 "해시 함수가없는 것"이 아닙니다. 세트에 사용자 정의 클래스가 포함 된 경우, 유효 Java가 방법을 표시 하도록 구현 hashCode
해야 HashSet
하지만,를 사용하는 경우 TreeSet
이를 작성 Comparable
하거나을 제공해야합니다 Comparator
. 클래스에 특정 순서가 없으면 문제가 될 수 있습니다.
나는 아주 작은 세트 / 맵 (<10 항목)에 때때로 TreeSet
(또는 실제로 TreeMap
) 사용했습니다. 하지만 실제로 얻는 것이 있는지 확인하지는 않았습니다. 큰 세트의 경우 그 차이가 상당 할 수 있습니다.
정렬이 필요한 TreeSet
경우 업데이트가 자주 발생하고 정렬 된 결과가 자주 나타나지 않더라도 때때로 내용을 목록이나 배열에 복사하여 정렬하는 것이 더 빠를 수 있습니다.
물론 HashSet 구현은 훨씬 빠르며 순서가 없기 때문에 오버 헤드가 적습니다. Java에서 다양한 Set 구현에 대한 올바른 분석은 http://java.sun.com/docs/books/tutorial/collections/implementations/set.html 에서 제공됩니다 .
거기에 대한 논의는 또한 Tree vs Hash 문제에 대한 흥미로운 '중간'접근법을 지적합니다. Java는 "삽입 지향"링크 목록이있는 HashSet 인 LinkedHashSet을 제공합니다. 즉, 링크 된 목록의 마지막 요소도 가장 최근에 해시에 삽입됩니다. 이를 통해 TreeSet의 비용 증가없이 정렬되지 않은 해시의 무질서를 피할 수 있습니다.
TreeSet의이 두 정렬 된 모음 (다른 쪽의 트리 맵) 중 하나이다. Red-Black 트리 구조를 사용하지만 자연 순서에 따라 요소가 오름차순으로 정렬됩니다. 선택적으로 Comparable 또는 Comparator를 사용하여 요소의 클래스에 의해 정의 된 순서에 의존하지 않고 순서에 대한 고유 규칙을 컬렉션에 제공 할 수있는 생성자로 TreeSet을 구성 할 수 있습니다.
그리고 그것은 LinkedHashSet은 모든 요소에서 이중의 링크리스트를 보관 유지 HashSet의 정렬 된 버전입니다. 반복 순서를 염려 할 때 HashSet 대신이 클래스를 사용하십시오. HashSet을 반복하면 순서를 예측할 수 없지만 LinkedHashSet을 사용하면 요소를 삽입 한 순서대로 요소를 반복 할 수 있습니다
기술적 고려 사항, 특히 성능과 관련하여 많은 답변이 제공되었습니다. 나에 따르면, 선택 TreeSet
과 HashSet
문제 중 하나.
그러나 오히려 개념적인 고려 사항 에 의해 선택이 우선되어야한다고 말하고 싶습니다 .
조작해야 할 객체에 대해 자연스러운 순서가 의미가 없다면를 사용하지 마십시오 TreeSet
.
구현하기 때문에 정렬 된 세트 SortedSet
입니다. 따라서 함수를 재정의 compareTo
해야하며 이는 함수를 반환하는 것과 일치해야합니다 equals
. 예를 들어 Student라는 클래스의 객체 집합이 있다면TreeSet
학생들 사이에 자연스러운 순서가 없기 때문에 가 의미가 . 당신은 그들의 평균 등급으로 주문할 수 있습니다. 그러나 이것은 "자연적인 주문"이 아닙니다. 함수compareTo
두 개체가 같은 학생을 나타낼 때뿐만 아니라 다른 두 학생이 같은 성적을 가질 때 0을 반환합니다. 두 번째 경우를 들어, equals
(두 개의 다른 학생들이 할 것 같은 학년 때 진정한 후자의 반환을하기로 결정하지 않는 한 false를 반환 equals
함수가 잘못된 의미를 말하고, 잘못된 의미가 없음.)
사이의 일관성을 유의하시기 바랍니다 equals
및 compareTo
선택 사항이지만 강력히 권장됩니다. 그렇지 않으면 인터페이스 계약 Set
이 깨져 코드가 다른 사람에게 오도되어 예기치 않은 동작이 발생할 수 있습니다.
이 링크 는이 질문에 관한 좋은 정보원이 될 수 있습니다.
오렌지를 먹을 수 있는데 왜 사과가 있습니까?
진심으로 남자와 여자-컬렉션이 크면 읽고 쓸데없이 쓰고, CPU 사이클을 지불하는 경우 컬렉션 선택은 더 나은 성능을 필요로하는 경우에만 관련이 있습니다. 그러나 대부분의 경우 이것은 실제로 중요하지 않습니다. 여기에는 몇 밀리 초가 걸리며 인간의 관점에서는 눈에 띄지 않습니다. 정말로 그렇게 중요하다면 어셈블러 나 C로 코드를 작성하지 않는 이유는 무엇입니까? [다른 토론을 큐]. 따라서 요점은 선택한 컬렉션을 사용하여 만족하면 문제를 해결하는 것입니다 (특히 작업에 가장 적합한 컬렉션 유형이 아니더라도). 소프트웨어는 가단성입니다. 필요한 경우 코드를 최적화하십시오. 밥 아저씨는 조기 최적화는 모든 악의 근원이라고 말합니다. 밥 아저씨가 그렇게 말합니다
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
public class HashTreeSetCompare {
//It is generally faster to add elements to the HashSet and then
//convert the collection to a TreeSet for a duplicate-free sorted
//Traversal.
//really?
O(Hash + tree set) > O(tree set) ??
Really???? Why?
public static void main(String args[]) {
int size = 80000;
useHashThenTreeSet(size);
useTreeSetOnly(size);
}
private static void useTreeSetOnly(int size) {
System.out.println("useTreeSetOnly: ");
long start = System.currentTimeMillis();
Set<String> sortedSet = new TreeSet<String>();
for (int i = 0; i < size; i++) {
sortedSet.add(i + "");
}
//System.out.println(sortedSet);
long end = System.currentTimeMillis();
System.out.println("useTreeSetOnly: " + (end - start));
}
private static void useHashThenTreeSet(int size) {
System.out.println("useHashThenTreeSet: ");
long start = System.currentTimeMillis();
Set<String> set = new HashSet<String>();
for (int i = 0; i < size; i++) {
set.add(i + "");
}
Set<String> sortedSet = new TreeSet<String>(set);
//System.out.println(sortedSet);
long end = System.currentTimeMillis();
System.out.println("useHashThenTreeSet: " + (end - start));
}
}