java中的散列如何工作?

时间:2013-06-19 16:11:42

标签: java algorithm hash hashcode

我试图在java中找出关于哈希的东西。 例如,如果我想在hashmap中存储一些数据,它是否会有一些带有hashvalues的底层哈希表? 或者,如果有人可以对哈希的工作方式做出一个简单明了的解释,我会非常感激。

6 个答案:

答案 0 :(得分:9)

HashMap基本上在内部实现为 Entry [] 的数组。如果你理解什么是linkedList,那么这个Entry类型只不过是一个链表实现。这种类型实际上存储了键和值。

要将元素插入数组,您需要索引。你如何计算指数?这是散列函数(hashFunction)出现的地方。在这里,您将一个整数传递给此哈希函数。现在要获取此整数,java会调用对象的 hashCode 方法,该方法将作为键添加到地图中。 此概念称为preHashing。

现在,一旦知道索引,就将元素放在此索引上。这基本上称为 BUCKET ,因此如果在Entry [0]中插入了元素,则表示它属于存储桶0。

现在假设hashFunction为您想要在地图中作为键插入的另一个对象返回相同的索引0。这是调用等于方法的地方,如果偶数等于返回true,则简单意味着存在 hashCollision 。因此,在这种情况下,由于Entry是一个链接列表实现,在此索引本身上,在此索引的已有条目上,您将向该链接列表添加一个节点(Entry)。所以底线,在hashColission上,通过链表在特定索引上有多个元素。

当您谈论从地图获取密钥时,会应用相同的情况。基于hashFunction返回的索引,如果只有一个条目,则在条目的链表上返回该条目,调用equals方法。

希望这有助于内部工作原理:)

答案 1 :(得分:4)

Java中的哈希值是由对象通过在Object类中声明的public int hashCode()的实现提供的,并且它是针对所有基本数据类型实现的。在自定义数据对象中实现该方法后,您无需担心如何在Java提供的各种数据结构中使用它们。

注意:实施该方法还需要以一致的方式实施public boolean equals(Object o)

答案 2 :(得分:3)

  

例如,如果我想在一个hashmap中存储一些数据,它是否会有一些带有hashvalues的底层哈希表?

HashMap 哈希表的一种形式(HashTable是另一种形式)。他们使用hashCode()密钥类型提供的equals(Object)HashMap方法。根据您希望关键字的行为方式,您可以使用hashCode实施的equals / java.lang.Object方法...或者您可以覆盖它们。

  

或者,如果有人可以对哈希的工作原理做出一个简单而简单的解释,我会非常感激。

我建议您阅读Hash Tables上的维基百科页面,了解它们的工作原理。 (FWIW,HashMapHashTable类使用“与链表单独链接”,以及其他一些调整以优化平均性能。)

散列函数通过将对象(即“密钥”)转换为整数来工作。它是如何做到这一点取决于实现者。但一种常见的方法是组合对象字段的哈希码,如下所示:

  hashcode = (..((field1.hashcode * prime) + field2.hashcode) * prime + ...)

其中prime是一个像31这样的小素数。关键是你可以为不同的密钥获得良好的哈希码值。你不想要的是许多键都散列到相同的值。这会导致“碰撞”并且对性能不利。

当您实现hashcodeequals方法时,您需要以满足以下约束的方式执行此操作以使哈希表正常工作:

 1. O1.equals(o2) => o1.hashcode() == o2.hashcode()
 2. o2.equals(o2) == o2.equals(o1)
 3. The hashcode of an object doesn't change while it is a key in a hash table.

值得注意的是,hashCode提供的默认equalsObject方法都是基于目标对象的身份。


  

“但是那里存储的哈希值在哪里?它不是HashMap的一部分,那么是否有一个与HashMap相关的数组?”

哈希值通常存储。而是根据需要计算

HashMap类的情况下,每个键的哈希码实际上都缓存在哈希链中。但这是一个性能优化...使哈希链搜索速度更快,并避免在哈希表调整大小时重新计算哈希值。但是如果你想要这种理解水平,你真的需要阅读源代码而不是问问题。

答案 3 :(得分:2)

这是Java中最基本的合同:the .equals()/.hashCode() contract。 其中最重要的部分是两个被视为.equals()的对象应返回相同的.hashCode()

反之亦然:不被视为等于的对象可能返回相同的哈希码。但它应该尽可能罕见。考虑以下.hashCode()实现,虽然完全合法,但实现可能存在的实现已经破坏了:

@Override
public int hashCode() { return 42; } // legal!!

虽然这个实现服从合同,但它几乎没用......因此开始使用好的哈希函数的重要性。

现在:Set合同规定Set不应包含重复元素;然而,Set实施的策略仍然存在......好吧,实施。如果你看一下Map的javadoc,你会注意到它的密钥可以通过一个名为.keySet()的方法来检索。因此,MapSet在这方面密切相关。

如果我们采用HashSet(最终是HashMap)的情况,它依赖于.equals().hashCode():在添加项目时,它首先计算此项的哈希码,并根据此哈希码尝试将项插入到给定的存储桶中。相比之下,TreeSet(和TreeMap)依赖于元素的自然排序(参见Comparable)。

但是,如果要插入一个对象,则此对象的哈希码将触发其插入非空哈希桶(请参阅合法但已损坏的.hashCode()实现上面),然后.equals()用于确定该对象是否真的是唯一的。

请注意,在内部,HashSetHashMap ...

答案 4 :(得分:0)

散列是一种在任何变量/对象的属性上应用任何函数/算法后为其分配唯一代码的方法。

答案 5 :(得分:0)

HashMap将键值对存储在Map.Entry静态嵌套类实现中。  HashMap使用哈希算法,并在put和get方法中使用hashCode()和equals()方法。

当我们通过传递键值对来调用put方法时,HashMap使用带有哈希值的Key hashCode()来找出  存储键值对的索引。该条目存储在LinkedList中,因此如果已经存在  现有条目,它使用equals()方法检查传递的键是否已经存在,如果是,它将覆盖  否则将创建一个新条目并存储此键值条目。

当我们通过传递Key调用get方法时,它再次使用hashCode()来查找索引 在数组中,然后使用equals()方法找到正确的Entry并返回其值。  下图将清楚地解释这些细节。

有关HashMap的其他重要信息是容量,负载因子,阈值大小调整。  HashMap的初始默认容量为16,负载系数为0.75。阈值乘以容量  根据负载系数,以及每当我们尝试添加条目时,如果地图尺寸大于阈值,  HashMap将地图的内容重新映射到容量更大的新数组中。  容量始终是2的幂,因此,如果您知道需要存储大量的键值对,  例如从数据库缓存数据时,最好以正确的容量初始化HashMap 和负载系数。