我一直喜欢树,它们漂亮的O(n*log(n))以及整齐有序的特点。 然而,我认识的每个软件工程师都曾经直截了当地问我为什么要使用TreeSet。从计算机科学背景来看,我认为你使用哪个并不是很重要,并且我也不想在哈希函数和桶(对于Java)方面进行操作。
在哪些情况下应该使用HashSet而不是TreeSet?
HashSet比TreeSet更快(对于大多数操作,例如添加、删除和包含,其时间复杂度是常数时间与对数时间的比较),但不像TreeSet那样提供排序保证。
SortedSet
)。first()
,last()
,headSet()
和tailSet()
等等。HashSet
和 TreeSet
之间。它是一个带有链表的哈希表,提供了按插入顺序迭代的功能,但与 TreeSet 提供的排序遍历不同。因此,使用哪种集合取决于您的需求,但我认为即使您需要有序集合,您仍应该首选 HashSet 来创建集合,然后将其转换为 TreeSet。
SortedSet<String> s = new TreeSet<String>(hashSet);
基于@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(1),因此它很重要。但是无法保持集合中对象的顺序。
TreeSet
对于需要维护顺序(按值而非插入顺序)的情况非常有用。但是,正如您所指出的,这样做会以访问元素的速度变慢为代价:基本操作的时间复杂度为O(log n)。
从TreeSet的Javadocs中可知:
此实现提供了基本操作(
add
,remove
和contains
)的对数时间复杂度(O(log n))。
1.HashSet允许空对象。
2.TreeSet不允许空对象。如果您尝试添加空值,它将抛出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
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);
- Dávid Horváthnull
,则会抛出NullPointerException
。 - M. JustinTreeSet
,则可以添加 null 值。根据 TreeSet.add(E e)
的说明:"如果指定的元素为 null 并且此 set 使用自然排序,或者其比较器不允许 null 元素,则抛出 NullPointerException 异常"。以下代码成功将 null 添加到 TreeSet 中:new TreeSet<>(Comparator.nullsLast(Comparator.naturalOrder())).add(null);
。 - M. JustinHashSet
的原因是其操作(平均而言)为O(1),而不是O(log n)。如果集合包含标准项,则无需“围绕哈希函数”进行操作,因为这已经为您完成了。如果集合包含自定义类,则必须实现hashCode
才能使用HashSet
(虽然《Effective Java》展示了如何实现),但如果您使用TreeSet
,则必须使其Comparable
或提供一个Comparator
。如果该类没有特定的顺序,这可能成为一个问题。TreeSet
(或实际上是TreeMap
),尽管我并没有检查是否真的有什么实际的收益。对于大型集合,差异可能相当大。TreeSet
是适当的,尽管即使在这种情况下,如果更新频繁且较少需要排序的结果,有时将内容复制到列表或数组中并对其进行排序可能会更快。如果插入的元素不足以导致哈希表频繁地重新散列(或者如果你的HashSet不能调整大小而出现冲突),那么HashSet肯定会给你带来恒定时间访问的好处。但对于经常增长或缩小的集合,根据实现方式,使用TreeSet可能会获得更好的性能。
如果我没记错,使用红黑树可以获得接近O(1)的平摊时间。Okasaki的书比我说的更好解释这个问题。(或者请参见他的出版物列表)
TreeSet是两个有序集合之一(另一个是TreeMap)。它使用红黑树结构(但您已经知道了),并保证元素按自然顺序升序排列。可选地,您可以使用具有构造函数的TreeSet来为集合提供您自己的规则,以确定顺序(而不是依赖于元素类定义的顺序),方法是使用Comparable或Comparator。
LinkedHashSet是HashSet的有序版本,它在所有元素中维护双向链接列表。在意迭代顺序时,请使用此类代替HashSet。当您遍历HashSet时,顺序是不可预测的,而LinkedHashSet允许您按照插入顺序遍历元素。
为什么只吃苹果,不尝试一下橙子呢?
亲爱的朋友们,如果你的集合非常大,并且被反复读写,而你还要支付CPU周期的费用,那么集合的选择只有在需要它更好地执行时才是相关的。然而,在大多数情况下,这并不真的重要——几毫秒的时间对人类来说是无关紧要的。如果真的那么重要,为什么不用汇编或C语言编写代码呢?[引出另一个讨论]。所以,如果你满意使用你选择的任何集合,并且解决了你的问题[即使它不是特定任务的最佳类型的集合],那就愉快地使用吧。软件是可塑的。必要时优化你的代码。鲍勃大叔说:过早地优化是万恶之源。鲍勃大叔这么说
Set<T>
引用使用您的集合,您也必须立即选择一个具体的类来实例化它,并提供所需的方法(equals、compare、hashcode)。没有优化,只是尝试做出适当的选择,以便您以后不必更改它。 - Michel Billaud