我试图在java中找出关于哈希的东西。 例如,如果我想在hashmap中存储一些数据,它是否会有一些带有hashvalues的底层哈希表? 或者,如果有人可以对哈希的工作方式做出一个简单明了的解释,我会非常感激。
答案 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,HashMap
和HashTable
类使用“与链表单独链接”,以及其他一些调整以优化平均性能。)
散列函数通过将对象(即“密钥”)转换为整数来工作。它是如何做到这一点取决于实现者。但一种常见的方法是组合对象字段的哈希码,如下所示:
hashcode = (..((field1.hashcode * prime) + field2.hashcode) * prime + ...)
其中prime
是一个像31
这样的小素数。关键是你可以为不同的密钥获得良好的哈希码值。你不想要的是许多键都散列到相同的值。这会导致“碰撞”并且对性能不利。
当您实现hashcode
和equals
方法时,您需要以满足以下约束的方式执行此操作以使哈希表正常工作:
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
提供的默认equals
和Object
方法都是基于目标对象的身份。
“但是那里存储的哈希值在哪里?它不是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()
的方法来检索。因此,Map
和Set
在这方面密切相关。
如果我们采用HashSet
(最终是HashMap
)的情况,它依赖于.equals()
和.hashCode()
:在添加项目时,它首先计算此项的哈希码,并根据此哈希码尝试将项插入到给定的存储桶中。相比之下,TreeSet
(和TreeMap
)依赖于元素的自然排序(参见Comparable)。
但是,如果要插入一个对象和,则此对象的哈希码将触发其插入非空哈希桶(请参阅合法但已损坏的.hashCode()
实现上面),然后.equals()
用于确定该对象是否真的是唯一的。
请注意,在内部,HashSet
是HashMap
...
答案 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 和负载系数。