我正在尝试了解Java中的哈希技术。例如,如果我想在HashMap中存储一些数据,它是否会有某种基础哈希表与哈希值?或者,如果有人能给出一个好的简单解释哈希是如何工作的,我将不胜感激。
我正在尝试了解Java中的哈希技术。例如,如果我想在HashMap中存储一些数据,它是否会有某种基础哈希表与哈希值?或者,如果有人能给出一个好的简单解释哈希是如何工作的,我将不胜感激。
HashMap基本上在内部实现为一个Entry[]数组。如果你了解什么是linkedList,那么这个Entry类型就是一个linkedList实现。这个类型实际上存储了键和值。
要将元素插入到该数组中,需要索引。如何计算索引?这就是哈希函数(hashFunction)派上用场的地方。在这里,您将一个整数传递给这个哈希函数。现在,为了获得这个整数,Java会调用要添加为映射键的对象的hashCode方法。这个概念被称为预哈希(preHashing)。
现在一旦知道了索引,就可以将元素放置在这个索引上。这基本上被称为BUCKET,因此如果元素被插入到Entry [0]中,则表示它落在bucket 0下。
现在假设哈希函数以相同的索引0返回另一个要插入为映射键的对象。这就是调用equals方法的地方,即使equals返回true,也意味着存在hashCollision。因此,在此情况下,由于Entry是链接列表实现,因此在此索引上,在已经可用的条目的链接列表中,您向此链接列表添加了一个节点(Entry)。因此,在哈希冲突时,通过链接列表在特定索引上存在不止一个元素。
当您要从映射中获取键时,也适用相同的情况。根据哈希函数返回的索引,如果只有一个条目,则返回该条目,否则调用条目的链接列表的equals方法。
希望这有助于了解它是如何工作的 :)
HashMap
是一种哈希表(HashTable
是另一种)。它们通过使用 HashMap
键类型提供的 hashCode()
和 equals(Object)
方法来工作。根据您希望键如何运作,您可以使用由 java.lang.Object
实现的 hashCode
/equals
方法...或者您可以覆盖它们。HashMap
和 HashTable
类使用“分离链接与链表”以及其他调整来优化平均性能。从 Java 8 开始,这些优化包括将长哈希链转换为二叉树。) 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.
Object
提供的默认 hashCode
和 equals
方法基于目标对象的身份。
public int hashCode()
实现的,该方法在Object
类中声明,并为所有基本数据类型实现。一旦您在自定义数据对象中实现了该方法,您就不需要担心它们如何在Java提供的各种数据结构中使用。public boolean equals(Object o)
。.equals()
/.hashCode()
合约。
其中最重要的部分是,被认为相等的两个对象应该返回相同的.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
...hashCode()
返回name.hasCode()
是可行的,但是返回一个常量hashCode
将允许找到所有具有当前名称的对象,即使它们在存储时具有其他名称。 - supercat.hashCode()
/.equals()
与可变字段,那么你有一个更深层次的问题要处理 ;) 我没有这个问题,因为我创建的所有实例都是不可变的,或者如果它们不是,就留给Object
处理.... - fgehashSet<T>
的类中存储对象,但线性搜索集合类型可能更好)。实际上,快速返回一个常量甚至不是最糟糕的“合法”hashCode
方法;一个需要很长时间才能得出每个实例相同答案的方法会更糟糕[顺便说一句,我认为.NET的某些类型意外地就是这样做的]。 - supercat哈希是一种在对变量/对象的属性应用任何函数/算法后为其分配唯一代码的方法。
HashMap在Map.Entry静态嵌套类实现中存储键值对。 HashMap使用哈希算法,并在put和get方法中使用hashCode()和equals()方法。
当我们通过传递键值对调用put方法时,HashMap使用Key hashCode()进行哈希处理以找到 存储键值对的索引。Entry存储在LinkedList中,因此如果已经存在条目,则使用equals()方法检查是否已经存在传递的键,如果是,则覆盖 值,否则创建一个新条目并存储此键值Entry。
当我们通过传递Key调用get方法时,它再次使用hashCode()查找索引 在数组中,然后使用equals()方法查找正确的Entry并返回其值。 下面的图像将清楚地解释这些细节。
HashMap的其他重要知识点包括容量、负载因子和阈值调整。HashMap的初始默认容量为16,负载因子为0.75。阈值是容量乘以负载因子,每当我们尝试添加一个条目时,如果映射的大小大于阈值,HashMap会将映射内容重新哈希到一个具有更大容量的新数组中。容量始终是2的幂,所以如果你知道你需要存储大量的键值对,例如从数据库缓存数据,最好使用正确的容量和负载因子来初始化HashMap。