申明: 本文章是翻译于国外一篇写的不错的文章,叙述了HashMap内部的工作,有少许的内容补充,若发现有错误的地方还请在评论区指出,谢谢
下面我们将看到的HashMap中的get() 方法put() 方法如何在内部工作。执行了哪些操作。如何进行哈希计算的。如何通过Key值获取Value值。如何存储key-value键值对。
HashMap中包含一个节点数组NodeArray,节点Node可以表示一个具有以下对象的类:
- int hash
- K key
- V value
- Node next
现在我们将看到它是如何工作的。首先,我们将看到散列过程(hashing process)。
哈希
散列法(hashing)是使用方法hashCode()方法将对象(Object)转换为整数形式的过程。该过程是必须要重写hashCode()方法的,以获得更好的HashMap性能。在这里,我将获取自己类的key,以便于我可以重写hashCode()方法来显示不同的内容。我的Key类是
//自定义Key类来重写hashCode()和equals()方法
class Key
{
String key;
Key(String key)
{
this.key = key;
}
@Override
public int hashCode()
{
return (int)key.charAt(0);
}
@Override
public boolean equals(Object obj)
{
return key.equals((String)obj);
}
}
这里重写的hashCode()方法将第一个字符的ASCII值作为哈希码返回。因此,只要key的第一个字符相同,哈希码就会相同。还有,HashMap也允许使用null键,所以null的哈希码始终为0。
hashCode()方法用于获取对象的哈希码。对象类的hashCode()方法以整数形式返回对象的内存引用.hashCode()方法在Object类中定义为public native hashCode()。它表示hashCode()的实现是native的(hashCode()获取的对象地址值是通过计算机底层与JVM互相传递数据,计算数据产生的逻辑地址值,与对象实际存放的物理地址值不同),因为在java中没有直接的方法来获取对象的引用(逻辑地址值),所以可以提供自己类里的hashCode()方法实现。
在HashMap中,hashCode()方法用于计算存储区,因此计算索引。
equals()方法
equals方法用于检查2个对象是否相等。此方法由Object类提供。你可以在类中重写它以获取您自己的实现需求。
HashMap使用equals() 来比较key是否相等。如果equals()方法返回true,则它们相等,否则不相等。
存储区
一个小的存储块是HashMap的节点数组的一个元素。它用于存储节点。两个或多个节点可以具有相同的存储块。在那种情况下,链接列表( link list )结构用于连接节点。存储区的容量不同。存储块和容量之间的关系如下:
capacity = number of buckets * load factor
单个存储块可以有多个节点,这取决于hashCode()方法。hashCode()方法越好,存储区的利用率就越高。
Hashmap中的索引计算
Key的哈希码能够创建一个数组。生成的哈希码可能在整数范围内,如果我们在这样的范围创建数组,那么它很容易导致异常OutOfMemoryException。所以我们生成的索引需要最小化数组的大小。基本上执行以下操作计算 index。
index = hashCode(key) & (n-1).
其中n是存储块的数量或数组的大小。在我们的示例中,我将n视为默认大小为16。
HashMap map = new HashMap();
HashMap:
插入键值对:将一个键值对09放在上面的HashMap的中
map.put(new Key("vishal"), 20);
步骤:
- 计算Key {“vishal”}的哈希码。它将生成为118。
- 使用索引方法计算索引将为6。
- 创建节点对象:
{
int hash = 118
// {"vishal"} 代表的是一个类,而不是一个字符串
Key key = {"vishal"}
Integer value = 20
Node next = null
}
- 如果此处没有其他对象,则将此对象放在索引6处。
现在的HashMap变成
插入另一个键值对:现在,把另一对,即:
map.put(new Key("sachin"), 30);
步骤:
- 计算Key {“sachin”}的hashCode。它将生成为115。
- 使用索引方法计算索引将为3。
- 创建节点对象:
{
int hash = 115
Key key = {"sachin"}
Integer value = 30
Node next = null
}
如果此处没有其他对象,则将此对象放在索引3处。
现在HashMap中变成:
在碰撞的情况下:现在,再放一对:
map.put(new Key("vaibhav"), 40);
脚步:
- 计算Key {“vaibhav”}的哈希码。它将生成为118。
- 使用索引方法计算索引将为6。
- 创建节点对象:
{
int hash = 118
Key key = {"vaibhav"}
Integer value = 40
Node next = null
}
- 如果此处没有其他对象,则将此对象放在索引6处。
- 在这种情况下,在索引6处找到节点对象 - 这是碰撞的情况。
- 在这种情况下,通过的hashCode()和equals()方法方法检查两个键是否相同。
- 如果键相同,将值替换为当前值。
-
否则,通过链表将此节点对象连接到上一个节点对象,并且两者都存储在索引6中。
现在,HashMap中变为:
现在让我们尝试一些获取方法来获取值.get(K key)方法用于通过其获取值。如果您不知道Key,则无法获取值。
获取关键萨钦的数据:
map.get(new Key("sachin"));
-
步骤:
- 计算Key {“sachin”}的哈希码。它将生成为115。
- 使用索引方法计算索引将为3。
- 转到数组的索引3并将第一个元素的键与给定键进行比较。如果两者都是等于则返回值,否则检查下一个元素是否存在。
- 在我们的例子中,它被发现是第一个元素,返回值是30。
- 获取Key vaibahv的数据:
map.get(new Key("vaibhav"));
步骤:
- 计算Key {“vaibhav”}的哈希码。它将生成为118。
- 使用索引方法计算索引将为6。
- 转到数组的索引6并将第一个元素的与给定键键进行比较。如果两者都是等于则返回值值,否则检查下一个元素是否存在。
- 在我们的例子中,它不是第一个元素,而节点对象的下一个元素不是空。
- 如果的下节点一个为空,则返回空值。
- 如果节点的下一个非空遍历到第二个元素并重复过程3,直到找不到键或者下一个不为空
// Java program to illustrate
// internal working of HashMap
import java.util.HashMap;
class Key {
String key;
Key(String key)
{
this.key = key;
}
@Override
public int hashCode()
{
int hash = (int)key.charAt(0);
System.out.println("hashCode for key: "
+ key + " = " + hash);
return hash;
}
@Override
public boolean equals(Object obj)
{
return key.equals(((Key)obj).key);
}
}
// Driver class
public class GFG {
public static void main(String[] args)
{
HashMap map = new HashMap();
map.put(new Key("vishal"), 20);
map.put(new Key("sachin"), 30);
map.put(new Key("vaibhav"), 40);
System.out.println();
System.out.println("Value for key sachin: " + map.get(new Key("sachin")));
System.out.println("Value for key vaibhav: " + map.get(new Key("vaibhav")));
}
}
控制台输出:
hashCode for key: vishal = 118
hashCode for key: sachin = 115
hashCode for key: vaibhav = 118
hashCode for key: sachin = 115
Value for key sachin: 30
hashCode for key: vaibhav = 118
Value for key vaibhav: 40
Java 8中的HashMap更改
我们现在知道,在哈希冲突的情况下,对象入口被存储为链表中的节点,等于()方法用于比较关键。在链表中找到正确密钥的比较是线性操作,因此在最坏的情况下,复杂度变为O(n)。
为解决此问题,Java 8哈希元素在达到特定阈值后使用平衡树而不是链接列表。这意味着HashMap首先在链接列表中存储条目对象,但是在散列中的项目数量大于某个阈值之后,散列将从使用链接列表变为平衡树,这将改善O的最坏情况性能。(n)至O(log n)。
重点
- 对于投入和GET方法,时间复杂度几乎是恒定的,直到没有进行重新散列。
- 在碰撞的情况下,即两个或更多个节点的索引相同,节点通过链接列表连接,即第二节点由第一节点引用,第三节点由第二节点引用,依此类推。
- 如果钥匙给定已存在于HashMap的中,则该值将替换为新值。
- 空键的哈希码为0。
- 获取具有其键的对象时,将遍历链接列表,直到键匹配或在下一个字段上找到空。
如果您发现任何不正确的内容,或者您想要分享有关上述内容的更多信息,请撰写评论。
原文 请 点击 这里