Hashset与Treeset

hey*_*hew 482 java hashset treeset

我一直喜欢树木,它们很好,O(n*log(n))而且它们整洁.然而,我所知道的每一位软件工程师都有针对性地问我为什么会使用TreeSet.从CS背景来看,我认为你所使用的并不重要,而且我不想乱用哈希函数和桶(在这种情况下Java).

在这情况下,我应该使用HashSetTreeSet

sac*_*tiw 850

HashSet比TreeSet快得多(对于大多数操作,例如add,remove和contains,常量时间与日志时间相比),但不提供像TreeSet这样的排序保证.

HashSet的

  • 该类为基本操作提供恒定的时间性能(添加,删除,包含和大小).
  • 它不能保证元素的顺序会随着时间的推移而保持不变
  • 迭代性能取决于HashSet 的初始容量加载因子.
    • 接受默认加载因子是非常安全的,但您可能希望指定的初始容量大约是您希望该组增长的大小的两倍.

TreeSet中

  • 保证基本操作的log(n)时间成本(添加,删除和包含)
  • 保证set的元素将被排序(升序,自然,或由您通过其构造函数指定的那个)(实现SortedSet)
  • 不提供迭代性能的任何调整参数
  • 提供了一些方便的方法来处理的有序集合一样first(),last(),headSet(),和tailSet()

重点:

  • 两者都保证元素的无重复收集
  • 通常,将元素添加到HashSet然后将集合转换为TreeSet以进行无重复的排序遍历通常会更快.
  • 这些实现都不是同步的.也就是说,如果多个线程同时访问一个集合,并且至少有一个线程修改了该集合,则必须在外部进行同步.
  • LinkedHashSet在某种意义上介于HashSet和之间TreeSet.实现为具有贯穿其的链表的哈希表,但是,它提供了插入顺序迭代,这与TreeSet保证的排序遍历不同.

因此,使用选择完全取决于您的需求,但我觉得即使您需要有序集合,您仍然应该更喜欢HashSet来创建Set,然后将其转换为TreeSet.

  • 例如 SortedSet<String> s = new TreeSet<String>(hashSet);

  • 只有我才发现肯定"HashSet比TreeSet快得多(常数时间与对数时间......)"显然是错误的?首先,这是关于时间复杂性,而不是绝对时间,并且O(1)可能在太多情况下比O(f(N))慢.其次,O(logN)是"几乎"O(1).如果对于许多常见情况,TreeSet的性能优于HashSet,我不会感到惊讶. (37认同)
  • 我只想提出Ivella的评论.时间复杂度与运行时间不同*O(1)并不总是优于O(2 ^ n).一个反常的例子说明了这一点:考虑使用哈希算法的哈希集,该哈希算法需要执行1万亿个机器指令(O(1))与10个元素的任何常见的冒泡排序(O(N ^ 2)平均/最差)实现.冒泡排序每次都会赢.重点是算法类教会每个人使用时间复杂度思考近似值,但在现实世界中常常使用常数因子*MATTER*. (22认同)
  • 也许这只是我,但不是建议首先将所有内容添加到一个hashset,然后将其转换为一个可怕的树集?1)如果您事先知道数据集的大小,则只能快速插入哈希集,否则您可能会多次支付O(n)重新哈希值.2)转换集合时,无论如何都要为TreeSet插入付费.(复仇,因为通过散列集迭代不是非常有效) (17认同)
  • 此建议基于以下事实:对于集合,您必须在添加项目之前检查项目是否重复; 因此,如果在树集上使用散列集,则可以节省时间,从而消除重复项.但是,考虑到为非重复项创建第二组的价格,重复项的百分比应该非常好,以克服这个价格并节省时间.当然,这适用于中型和大型集合,因为对于一个小集合,树集可能比散列集更快. (5认同)
  • @PeterOehlert:请为此提供基准.我理解你的观点,但两个集合之间的差异对于小集合大小几乎没有影响.一旦集合增长到一定程度,实现就越重要,log(n)就成了一个问题.一般来说,散列函数(甚至是复杂函数)比几个缓存未命中(几乎每个访问级别的大树上都有)更快地查找/访问/添加/修改叶子.至少这是我在Java中使用这两套的经验. (5认同)
  • 如果您的示例将新的TreeSet分配给SortedSet类型,也许会更有意义. (4认同)
  • @matdumsa TreeSet在子级和父级方向都有指针,肯定会付出代价. (2认同)

小智 38

尚未提及的一个优点TreeSet是它具有更大的"局部性",这是说(1)如果两个条目在序列中附近,则TreeSet它们在数据结构中彼此靠近,因此在存储器中; (2)这种放置利用了局部性原理,即相似频率的应用程序经常访问类似数据.

这与a形成对比,a HashSet无论键是什么,它都会将条目分布在整个内存中.

当从硬盘读取延迟成本数千次的从高速缓存或内存,当数据真正与当地访问时,阅读成本TreeSet可以是一个更好的选择.

  • 与Java无关.该组的元素无论如何都是对象,并指向其他地方,所以你不会节省太多的东西. (6认同)
  • 你能证明_if这两个条目在顺序附近,TreeSet将它们放在数据结构中,因此在memory_? (3认同)
  • 除了关于 Java 中普遍缺乏局部性的其他评论之外,OpenJDK 的“TreeSet”/“TreeMap”实现并未进行局部性优化。虽然可以使用 4 阶 b 树来表示红黑树,从而提高局部性和缓存性能,但这并不是实现的工作方式。相反,每个节点都存储一个指向它自己的键、它自己的值、它的父节点以及它的左右子节点的指针,这在 [TreeMap.Entry 的 JDK 8 源代码](http://hg.openjdk.java .net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/TreeMap.java#l2048)。 (2认同)

duf*_*ymo 25

HashSet是O(1)访问元素,所以它确实很重要.但是不可能保持集合中对象的顺序.

TreeSet如果维护订单(就价值而非订单顺序)对您很重要,则非常有用.但是,正如您已经注意到的那样,您正在交易订单,以便更慢地访问元素:O(log n)用于基本操作.

javadocsTreeSet:

此实现提供了基本的操作保证的log(n)的时间成本(add,removecontains).


小智 21

1.HashSet允许空对象.

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
Run Code Online (Sandbox Code Playgroud)

  • ts.add(null)如果在TreeSet中添加null作为第一个Object,它将在TreeSet的情况下正常工作.之后添加的任何对象都会在Comparator的compareTo方法中产生NullPointerException. (3认同)
  • 你真的不应该在你的集合中添加`null`. (2认同)

kie*_*tos 21

基于@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               ?
????????????????????????????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)


Kat*_*one 13

大多数使用的原因HashSet是操作(平均)O(1)而不是O(log n).如果集合包含标准项目,那么您将不会"乱用哈希函数",因为已经为您完成了.如果集合包含自定义类,则必须实现hashCode使用HashSet(尽管Effective Java显示了如何使用),但如果使用a TreeSet,则必须创建Comparable或提供Comparator.如果班级没有特定的订单,这可能是一个问题.

我有时使用TreeSet(或实际TreeMap)非常小的集/地图(<10项),虽然我没有检查是否有任何实际的收益.对于大型套装,差异可能相当大.

现在,如果你需要排序,那么TreeSet是合适的,尽管即使这样,如果频繁更新并且对排序结果的需求很少,有时将内容复制到列表或数组并对它们进行排序可能会更快.


Jas*_*rue 11

如果您没有插入足够的元素来导致频繁的重新散列(或者碰撞,如果您的HashSet无法调整大小),HashSet肯定会为您提供持续时间访问的好处.但是在具有大量增长或缩减的集合上,使用Treesets实际上可能会获得更好的性能,具体取决于实现.

如果记忆为我服务,摊销时间可以接近O(1),功能红黑树.Okasaki的书会有比我能说的更好的解释.(或者看他的出版物清单)


Jos*_*man 7

当然,HashSet实现要快得多 - 开销较少,因为没有排序.http://java.sun.com/docs/books/tutorial/collections/implementations/set.html提供了对Java中各种Set实现的良好分析.

那里的讨论还指出了树与哈希问题的一个有趣的"中间立场"方法.Java提供了一个LinkedHashSet,它是一个HashSet,其中有一个"面向插入"的链表,也就是说,链表中的最后一个元素也是最近插入Hash的.这使您可以避免无序散列的不正常,而不会导致TreeSet的成本增加.