我在学位课程中听说如果新的Key条目与另一个条目冲突,HashTable
将在“下一个可用”存储桶中放入一个新条目。
如果在使用碰撞密钥调用一个碰撞时发生此碰撞,HashTable
如何仍然返回正确的值?
我假设Keys
是String
类型,hashCode()
返回由Java生成的默认值。
如果我实现自己的散列函数并将其用作查找表的一部分(即HashMap
或Dictionary
),那么处理碰撞的策略是什么?
我甚至看过有关素数的注释! Google搜索中的信息不太明确。
答案 0 :(得分:84)
哈希表以两种方式之一处理冲突。
选项1:通过让每个存储桶包含对该存储桶进行哈希处理的元素的链接列表。这就是错误的哈希函数可以使哈希表中的查找非常慢的原因。
选项2:如果哈希表条目全部已满,则哈希表可以增加它拥有的桶数,然后重新分配表中的所有元素。哈希函数返回一个整数,哈希表必须获取哈希函数的结果,并根据表的大小对其进行修改,这样就可以确定它将进入存储区。因此,通过增加大小,它将重新运行并运行模数计算,如果幸运的话,可以将对象发送到不同的桶。
Java在其哈希表实现中使用选项1和2。
答案 1 :(得分:65)
当你谈到“如果新的Key条目与另一个条目发生碰撞时,Hash Table会在'下一个可用'存储桶中放置一个新条目。”,你谈论的是碰撞的开放式寻址策略散列表的分辨率。
哈希表有几种解决冲突的策略。
第一种大方法要求将键(或指向它们的指针)与相关值一起存储在表中,其中还包括:
处理碰撞的另一个重要方法是动态调整大小,这还有几种方法:
编辑:以上内容均来自wiki_hash_table,您应该去那里了解更多信息。
答案 2 :(得分:17)
有多种技术可用于处理碰撞。我将解释其中的一些
<强>链强> 在链接中,我们使用数组索引来存储值。如果第二个值的哈希码也指向同一个索引,那么我们用链表替换该索引值,指向该索引的所有值都存储在链表中,实际数组索引指向链表的头部。 但是,如果只有一个哈希码指向数组的索引,那么该值将直接存储在该索引中。检索值时应用相同的逻辑。这在Java HashMap / Hashtable中用于避免冲突。
线性探测:当表中的索引多于要存储的值时,使用此技术。线性探测技术适用于保持递增的概念,直到找到空槽。伪代码如下所示:
index = h(k)
while( val(index) is occupied)
index = (index+1) mod n
双重散列技术:在这种技术中,我们使用两个散列函数h1(k)和h2(k)。如果h1(k)处的时隙被占用,则第二个散列函数h2(k)用于递增索引。伪代码如下所示:
index = h1(k)
while( val(index) is occupied)
index = (index + h2(k)) mod n
线性探测和双重散列技术是开放寻址技术的一部分,只有在可用的时隙超过要添加的项目数时才能使用它。它比链接占用更少的内存,因为这里没有使用额外的结构,但由于大量的移动发生直到我们找到一个空槽,它的速度很慢。同样在开放式寻址技术中,当一个项目从一个插槽中移除时,我们放置一个墓碑,表明该项目已从此处移除,这就是为什么它是空的。
有关详细信息,请参阅this site。
答案 3 :(得分:16)
我强烈建议您阅读最近出现在HackerNews上的博文: How HashMap works in Java
简而言之,答案是
如果两个不同,将会发生什么 HashMap关键对象具有相同的功能 哈希码?
它们将存储在同一个桶中但是 没有链表的下一个节点。和钥匙 将使用equals()方法 识别正确的键值对 HashMap中。
答案 4 :(得分:7)
我在学位课上听说过 HashTable将放入一个新条目 新的“下一个可用”桶 密钥输入与另一个冲突。
实际上并非如此,至少对于Oracle JDK而言( 实现细节可能因API的不同实现而异)。相反,每个存储桶包含Java 8之前的条目的链接列表,以及Java 8或更高版本中的平衡树。
那么HashTable怎么样呢? 如果这样,返回正确的值 呼叫一个时发生冲突 用碰撞键回来?
它使用equals()
来查找实际匹配的条目。
如果我实现自己的散列函数 并将其用作查找表的一部分 (即HashMap或Dictionary),什么 存在处理的策略 碰撞?
有各种各样的优点和缺点的碰撞处理策略。 Wikipedia's entry on hash tables提供了一个很好的概述。
答案 5 :(得分:5)
自Java 8以来的更新: Java 8使用自平衡树进行冲突处理,将最坏的情况从O(n)改进为O(log n)进行查找。自平衡树的使用是Java 8中引入的,它是对链接(直到Java 7之前使用)的改进,后者使用链表,并且在查找时需要O(n)的最坏情况(因为它需要遍历)列表)
要回答问题的第二部分,插入是通过将给定元素映射到哈希图的基础数组中的给定索引来完成的,但是,当发生冲突时,仍必须保留所有元素(存储在辅助元素中)数据结构,而不仅仅是在基础数组中替换)。通常,这是通过将每个数组组件(插槽)作为辅助数据结构(又称为存储桶)来完成的,然后将元素添加到位于给定数组索引上的存储桶中(如果存储桶中不存在键,在这种情况下将其替换)。
在查找过程中,键被散列到其对应的数组索引,并在给定存储桶中搜索与(精确)键匹配的元素。由于存储桶不需要处理冲突(直接比较键),因此可以解决冲突问题,但是这样做的代价是必须对辅助数据结构执行插入和查找。关键点在于,在哈希图中,密钥和值均被存储,因此即使哈希发生冲突,也可以直接比较密钥的相等性(在存储桶中),从而可以在存储桶中唯一标识。>
Collission-handling在没有冲突处理的情况下从O(1)到O(n)进行链接(链接列表用作辅助数据结构)和O( log n)表示自平衡树。
参考文献:
Java 8对HashMap进行了以下改进/更改 发生高碰撞的物体。
已删除Java 7中添加的替代String哈希函数。
包含大量冲突键的存储桶将它们的条目存储在平衡树中,而不是存储在链接列表中 达到一定的阈值。
以上更改可确保在最坏的情况下O(log(n))的性能 (https://www.nagarro.com/en/blog/post/24/performance-improvement-for-hashmap-in-java-8)
答案 6 :(得分:3)
由于Java HashMap正在使用哪种算法(在Sun / Oracle / OpenJDK实现中)存在一些混淆,这里是相关的源代码片段(来自OpenJDK,1.6.0_20,在Ubuntu上):
/**
* Returns the entry associated with the specified key in the
* HashMap. Returns null if the HashMap contains no mapping
* for the key.
*/
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;
}
在查找表格中的条目时,例如从get()
,containsKey()
和其他一些条目中调用此方法(引用来自第355到371行)。这里的for循环遍历由条目对象形成的链表。
这里是条目对象的代码(第691-705行+ 759行):
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
// (methods left away, they are straight-forward implementations of Map.Entry)
}
在此之后出现addEntry()
方法:
/**
* Adds a new entry with the specified key, value and hash code to
* the specified bucket. It is the responsibility of this
* method to resize the table if appropriate.
*
* Subclass overrides this to alter the behavior of put method.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
这会在存储桶前面添加新条目,并带有指向旧第一个条目的链接(如果没有,则为null)。类似地,removeEntryForKey()
方法遍历列表并负责删除一个条目,使列表的其余部分保持完整。
所以,这里是每个存储桶的链接条目列表,我非常怀疑它从_20
更改为_22
,因为从1.2开始就是这样。
(此代码为(c)1997-2007 Sun Microsystems,可在GPL下使用,但为了更好地使用原始文件,包含在src.zip中,包含在Sun / Oracle的每个JDK中,以及OpenJDK中。)< / p>
答案 7 :(得分:2)
它将使用equals方法来查看密钥是否存在,特别是如果同一个存储桶中有多个元素。
答案 8 :(得分:1)
这是java中一个非常简单的哈希表实现。仅在实现put()
和get()
,但您可以轻松添加任何您喜欢的内容。它依赖于由所有对象实现的java hashCode()
方法。你可以轻松创建自己的界面,
interface Hashable {
int getHash();
}
并强制它由键实现,如果你愿意的话。
public class Hashtable<K, V> {
private static class Entry<K,V> {
private final K key;
private final V val;
Entry(K key, V val) {
this.key = key;
this.val = val;
}
}
private static int BUCKET_COUNT = 13;
@SuppressWarnings("unchecked")
private List<Entry>[] buckets = new List[BUCKET_COUNT];
public Hashtable() {
for (int i = 0, l = buckets.length; i < l; i++) {
buckets[i] = new ArrayList<Entry<K,V>>();
}
}
public V get(K key) {
int b = key.hashCode() % BUCKET_COUNT;
List<Entry> entries = buckets[b];
for (Entry e: entries) {
if (e.key.equals(key)) {
return e.val;
}
}
return null;
}
public void put(K key, V val) {
int b = key.hashCode() % BUCKET_COUNT;
List<Entry> entries = buckets[b];
entries.add(new Entry<K,V>(key, val));
}
}
答案 9 :(得分:1)
有各种冲突解决方法。其中有一些是单独链接,开放寻址,罗宾汉哈希,杜鹃哈希等。
Java使用Separate Chaining来解决Hash表中的冲突。这是一个很好的链接,它是如何发生的: http://javapapers.com/core-java/java-hashtable/