Java中HashSet.contains()的时间复杂度性能如何?

Java中HashSet.contains()的时间复杂度性能如何?,java,collections,Java,Collections,我倾向于认为该方法在恒定时间内执行。它只是获取对象的哈希代码,然后在哈希表中查找它 首先,有人能证实这是否属实吗 第二,如果这是真的,是否存在冲突的风险,两个对象可能具有相同的哈希代码,因此哈希集在只有一个时认为它同时具有这两个代码?它在O(1)预期时间内运行,就像任何哈希表一样(假设哈希函数是合适的)。它由一个HashMap支持,其中键是对象 两个对象可能具有相同的哈希代码,但是HashSet不会认为它们是相同的,除非这些对象的equals方法表示它们是相同的(即返回true) 包含HashM

我倾向于认为该方法在恒定时间内执行。它只是获取对象的哈希代码,然后在哈希表中查找它

首先,有人能证实这是否属实吗

第二,如果这是真的,是否存在冲突的风险,两个对象可能具有相同的哈希代码,因此哈希集在只有一个时认为它同时具有这两个代码?

它在
O(1)
预期时间内运行,就像任何哈希表一样(假设哈希函数是合适的)。它由一个
HashMap
支持,其中键是对象

两个对象可能具有相同的哈希代码,但是
HashSet
不会认为它们是相同的,除非这些对象的
equals
方法表示它们是相同的(即返回true)

包含
HashMap
方法调用(间接)
getEntry
,其中键是希望知道其是否在
HashSet
中的
对象


如下所示,两个对象可以存储在
HashMap
/
HashSet
中,即使它们的键被hash函数映射到相同的值。该方法迭代具有相同散列值的所有键,并对每个键执行
equals
,以找到匹配的键

final Entry<K,V> getEntry(Object key) {
         int hash = (key == null) ? 0 : hash(key.hashCode());
         for (Entry<K,V> e = table[indexFor(hash, table.length)];
              e != null;
              e = e.next) {
             Object k;
             if (e.hash == hash &&
                 ((k = e.key) == key || (key != null && key.equals(k))))
                 return e;
         }
         return null;
     }
final Entry getEntry(对象键){
int hash=(key==null)?0:hash(key.hashCode());
for(条目e=表[indexFor(hash,table.length)];
e!=null;
e=e.next){
对象k;
如果(e.hash==hash&&
((k=e.key)==key | |(key!=null&&key.equals(k)))
返回e;
}
返回null;
}

对于Java 8,contains的最差性能为O(logn),对于Java 7,为O(n),但平均性能更接近O(1)。这是因为hashset由hashmap支持,因此具有与hashmap查找相同的效率(即hashmap.get(…)。hashmap中的实际映射是常量时间(O(1)),但处理冲突的需要会给logn带来成本。也就是说,散列到同一数组索引的多个元素必须存储在辅助数据结构(也称为bucket)中,并且正是这个bucket决定了最坏情况下的性能。在Java中,hashmap冲突处理是使用自平衡树实现的

自平衡树为所有操作保证O(logn),因此,在hashmap(和hashset)中插入和查找的总成本为O(1)+O(logn)=O(logn)。Java 8中引入了自平衡树来处理冲突,这是对链接(在Java 7之前一直使用)的一种改进,链接使用链表,查找和插入的最坏情况是O(n)(因为它需要遍历列表)。请注意,链接将具有恒定的插入时间(与查找相反),因为可以在O(1)中将元素添加到链表中,但在hashmap的情况下,set属性(无重复项)将强制应用于链表,因此,在插入的情况下,它也需要遍历链表,以确保元素在列表/存储桶中不存在,并且插入和查找的结果都是O(n)

参考资料:

此类实现了Set接口,该接口由哈希表支持 (实际上是一个HashMap实例)。

包含大量碰撞键的存储桶将存储它们的 平衡树中的条目,而不是在特定 已达到阈值。 ()


至少在理论上,你是对的。不过,对哈希同义词和表溢出的所有警告都是如此。contains的时间复杂性与get相同。请参阅此处的答案:进一步讨论“该方法迭代具有相同散列值的所有键”,因此它不是
O(1)
,是吗?@AliLotfi
预期的
时间是O(1),因为散列集的每个存储桶中的键的平均数受一个小常量的限制。在最坏的情况下(如果所有键都映射到同一个bucket),搜索将需要线性时间,但除非您有一个糟糕的hashCode方法,否则最坏的情况是不会发生的。如果您的hash函数如此糟糕,则会有更大的问题。最坏的复杂度将是O(1)和equals函数的最大值。例如,对于大小为W的查询字符串,一组字符串的时间复杂度最差为O(W)。我想看看导致这一决定的基准代码。这似乎是一种净悲观。我非常怀疑他们在博客文章中显示的基准是否在设置适当的负载因子时有效,或者是否反对双重散列(但另一方面,我无法想象他们不考虑这一点;最后,我仍然感到困惑)。@KonradRudolph注意到,只有在严重碰撞的情况下,链表才被“交换”为树,这可能是hashCode方法的自定义重写的结果。我认为这是合理的。如果你说的是“悲观”,那么,如果我们假设的是一般情况,那么预期的碰撞量很低,并且使用了链锁。即使不使用阈值,在平均情况下,任何给定bucket的大小都将保持较低,hance我怀疑树与列表所需的额外比较是否会产生很大的差异。然而,在许多元素的情况下,树的好处是显而易见的。