我一直很喜欢树木,那很好O(n*log(n))
和它们的整洁。但是,我所知道的每个软件工程师都有针对性地问我为什么会使用TreeSet
。从CS背景来看,我认为你所使用的并不重要,而且我不想乱用哈希函数和桶(在Java
的情况下)。
在哪种情况下,我应该HashSet
使用TreeSet
?
答案 0 :(得分:839)
HashSet比TreeSet快得多(对于大多数操作,例如add,remove和contains,常量时间与日志时间相比),但不提供像TreeSet这样的排序保证。
SortedSet
)first()
,last()
,headSet()
和tailSet()
等HashSet
和TreeSet
之间。实现为具有贯穿其的链表的哈希表,但是,它提供了插入顺序的迭代,这与TreeSet保证的排序遍历不同。因此,使用选择完全取决于您的需求,但我觉得即使您需要有序集合,您仍然应该更喜欢使用HashSet来创建Set,然后将其转换为TreeSet。
SortedSet<String> s = new TreeSet<String>(hashSet);
答案 1 :(得分:39)
TreeSet
尚未提及的一个优点是它具有更大的“地点”,这是说(1)如果订单附近有两个条目,TreeSet
将它们放在附近的简写数据结构中相互之间,因此在内存中; (2)这种放置利用了局部性原则,即相似数据通常由具有相似频率的应用程序访问。
这与HashSet
形成鲜明对比,TreeSet
会将条目分布在整个内存中,无论其密钥是什么。
当从硬盘驱动器读取的延迟成本是从缓存或RAM读取的成本的数千倍,并且当真正使用位置访问数据时,{{1}}可能是更好的选择。< / p>
答案 2 :(得分:25)
HashSet
是O(1)访问元素,所以它确实很重要。但是不可能维持集合中对象的顺序。
TreeSet
非常有用。但是,正如您所指出的那样,您正在交易订单,以便更慢地访问元素:O(log n)用于基本操作。
此实施为基本操作(
add
,remove
和contains
)提供了有保证的log(n)时间成本。
答案 3 :(得分:21)
1.HashSet允许空对象。
2.TreeSet不允许null对象。如果您尝试添加null值,则会抛出NullPointerException。
3.HashSet比TreeSet快得多。
e.g。
TreeSet<String> ts = new TreeSet<String>();
ts.add(null); // throws NullPointerException
HashSet<String> hs = new HashSet<String>();
hs.add(null); // runs fine
答案 4 :(得分:20)
基于@shevchyk在地图上的可爱visual answer,这是我的看法:
╔══════════════╦═════════════════════╦═══════════════════╦═════════════════════╗
║ 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 ║
╚══════════════╩═══════════════════════════════════════════════════════════════╝
答案 5 :(得分:13)
大多数人使用HashSet
的原因是操作(平均)是O(1)而不是O(log n)。如果集合包含标准项目,那么您将不会“乱用哈希函数”,因为已经为您完成了。如果集合包含自定义类,则必须实现hashCode
以使用HashSet
(尽管Effective Java显示了如何),但是如果使用TreeSet
则必须使用Comparable
Comparator
}或提供TreeSet
。如果班级没有特定的订单,这可能是个问题。
我有时会使用TreeMap
(或实际上TreeSet
)用于非常小的集合/地图(&lt; 10项目),尽管我还没有检查过这样做是否有任何实际好处。对于大型套装,差异可能相当大。
现在,如果您需要排序,那么{{1}}是合适的,尽管如果更新频繁且需要排序结果很少,有时将内容复制到列表或数组并对它们进行排序可以更快。
答案 6 :(得分:10)
如果您没有插入足够的元素来导致频繁的重新散列(或者碰撞,如果您的HashSet无法调整大小),HashSet肯定会为您提供持续时间访问的好处。但是在具有大量增长或缩减的集合上,使用Treesets实际上可以获得更好的性能,具体取决于实现。
如果记忆为我服务,摊销时间可以接近O(1),并带有功能性的红黑树。 Okasaki的书会有比我能说的更好的解释。 (或参见his publication list)
答案 7 :(得分:7)
那里的讨论还指出了树与哈希问题的一个有趣的“中间立场”方法。 Java提供了一个LinkedHashSet,它是一个HashSet,其中有一个“面向插入”的链表,也就是说,链表中的最后一个元素也是最近插入Hash的。这使您可以避免无序散列的不正常,而不会导致TreeSet的成本增加。
答案 8 :(得分:4)
TreeSet 是两个已排序集合之一(另一个是 TreeMap的)。它使用红黑树结构(但你知道),并保证 根据自然顺序,元素将按升序排列。 (可选) 你可以用一个构造函数构造一个TreeSet,让你给你的集合 对订单应该是什么的自己的规则(而不是依赖于定义的顺序 通过元素'类)使用Comparable或Comparator
和 LinkedHashSet 是HashSet的有序版本 在所有元素中维护一个双向链表。使用此类而不是HashSet 当你关心迭代顺序时。当你遍历一个HashSet时 order是不可预测的,而LinkedHashSet允许你遍历元素 按插入顺序
答案 9 :(得分:3)
基于技术考虑,特别是在性能方面,已经给出了很多答案。
据我所知,TreeSet
和HashSet
之间的选择至关重要。
但我宁愿说选择应首先由 概念 考虑因素驱动。
如果对于您需要操作的对象,自然顺序没有意义,那么请不要使用TreeSet
。
它是一个有序集,因为它实现了SortedSet
。所以这意味着你需要覆盖函数compareTo
,它应该与返回函数equals
一致。例如,如果你有一组名为Student的类的对象,那么我认为TreeSet
没有意义,因为学生之间没有自然的顺序。您可以按平均等级订购它们,好吧,但这不是“自然排序”。函数compareTo
不仅会在两个对象代表同一个学生时返回0,而且当两个不同的学生具有相同的成绩时也会返回0。对于第二种情况,equals
将返回false(除非您决定在两个不同的学生具有相同成绩时使后者返回true,这将使equals
函数具有误导性含义,而不是说错误的意思。)
请注意equals
和compareTo
之间的一致性是可选的,但强烈建议。否则,接口Set
的合同就会被破坏,使您的代码误导其他人,从而也可能导致意外行为。
此 link可能是有关此问题的良好信息来源。
答案 10 :(得分:3)
为什么在有橘子的时候有苹果?
严肃的家伙和女孩 - 如果你的收藏很大,读取和写入数以万计的时间,并且你需要为CPU周期付费,那么只有你需要它才能更好地选择收藏品。然而,在大多数情况下,这并不重要 - 在这里几毫秒,人类没有注意到这一点。如果它真的很重要,为什么你不用汇编程序或C编写代码? [提出另一个讨论]。所以重点是,如果您对使用您选择的任何集合感到高兴,并且它解决了您的问题[即使它不是特定的任务收集类型]也会让您自己解决问题。该软件具有可塑性。必要时优化您的代码。鲍勃叔叔说过早优化是万恶之源。 Uncle Bob says so
答案 11 :(得分:1)
消息编辑(完全重写)当订单无关紧要时,就是这样。两者都应该给出Log(n) - 看看它们是否比另一个快5%以上是有用的。 HashSet可以在循环中给出O(1)测试,以揭示它是否。
答案 12 :(得分:1)
即使在11年之后,也没有人想到提及非常重要差异。
您是否认为如果HashSet
等于TreeSet
,那么相反也成立了吗?看一下这段代码:
TreeSet<String> treeSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
HashSet<String> hashSet = new HashSet<>();
treeSet.add("a");
hashSet.add("A");
System.out.println(hashSet.equals(treeSet));
System.out.println(treeSet.equals(hashSet));
尝试猜测输出,然后将鼠标悬停在摘要下方,以查看实际输出是什么。准备?在这里,您去了:
假
是
是的,对于与equals不一致的比较器,它们没有等价关系。原因是TreeSet
使用比较器确定等效性,而HashSet
使用equals
。他们在内部使用HashMap
和TreeMap
,因此您也应该在提到的Map
中使用此行为。
答案 13 :(得分:-3)
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));
}
}