在本文中,我们将看到 hashmap 的 get 和 put 方法在内部是如何工作的。执行哪些操作。散列是如何完成的。如何通过键获取值。键值对的存储方式。
在上一篇文章中,HashMap 包含一个 Node 数组,Node 可以表示一个具有以下对象的类:
- 整数哈希
- K键
- V值
- 下一个节点
现在我们将看到它是如何工作的。首先我们将看到散列过程。
散列
散列是使用 hashCode() 方法将对象转换为整数形式的过程。有必要正确编写 hashCode() 方法以获得更好的 HashMap 性能。在这里,我使用我自己的类的键,以便我可以覆盖 hashCode() 方法来显示不同的场景。我的关键课是
//custom Key class to override hashCode()
// and equals() method
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 也允许空键,所以空的哈希码将始终为 0。
hashCode() 方法
hashCode() 方法用于获取对象的哈希码。对象类的 hashCode() 方法以整数形式返回对象的内存引用。 hashCode() 方法的定义是 public native hashCode()。它表明 hashCode() 的实现是原生的,因为在Java没有任何直接的方法来获取对象的引用。可以提供您自己的 hashCode() 实现。
在 HashMap 中,hashCode() 用于计算桶,从而计算索引。
等于() 方法
equals 方法用于检查 2 个对象是否相等。该方法由 Object 类提供。您可以在您的类中覆盖它以提供您自己的实现。
HashMap 使用 equals() 来比较键是否相等。如果 equals() 方法返回 true,则它们相等,否则不相等。
桶
桶是 HashMap 数组的一个元素。它用于存储节点。两个或多个节点可以具有相同的存储桶。在这种情况下,使用链表结构来连接节点。桶的容量不同。桶和容量之间的关系如下:
capacity = number of buckets * load factor
一个桶可以有多个节点,这取决于 hashCode() 方法。你的 hashCode() 方法越好,你的桶就会被更好地利用。
Hashmap中的索引计算
键的哈希码可能大到足以创建一个数组。生成的hash码可能在整数范围内,如果我们为这样的范围创建数组,那么很容易导致outOfMemoryException。所以我们生成索引来最小化数组的大小。基本上执行以下操作来计算索引。
index = hashCode(key) & (n-1).
其中 n 是桶数或数组大小。在我们的示例中,我将 n 视为默认大小,即 16。
为什么用上面的方法来计算指数
使用按位 AND运算符类似于进行位掩码,其中仅考虑散列整数的低位,这反过来提供了一种基于散列图长度计算模数的非常有效的方法。
- 初始为空的hashMap:这里hashmap的大小取为16。
HashMap map = new HashMap();
- 哈希映射:
- 插入键值对:将一个键值对放在上面的 HashMap 中
map.put(new Key("vishal"), 20);
- 脚步:
- 计算 Key {“vishal”} 的哈希码。它将生成为 118。
- 使用指数法计算指数为6。
- 创建一个节点对象:
{
int hash = 118
// {"vishal"} is not a string but
// an object of class Key
Key key = {"vishal"}
Integer value = 20
Node next = null
}
- 如果没有其他对象出现在那里,则将此对象放在索引 6 处。
- 插入另一个键值对:现在,放置另一个键值对,即
map.put(new Key("sachin"), 30);
- 脚步:
- 计算 Key {“sachin”} 的 hashCode。它将生成为 115。
- 使用索引法计算索引为3。
- 创建一个节点对象:
{
int hash = 115
Key key = {"sachin"}
Integer value = 30
Node next = null
}
- 在发生碰撞的情况下:现在,放置另一对,即
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 方法来获取一个值。 get(K key) 方法用于通过其键获取值。如果您不知道密钥,则无法获取值。
- 获取关键 sachin 的数据:
map.get(new Key("sachin"));
- 脚步:
- 计算 Key {“sachin”} 的哈希码。它将生成为 115。
- 使用索引法计算索引为3。
- 转到数组的索引 3 并将第一个元素的键与给定的键进行比较。如果两者相等则返回值,否则检查下一个元素是否存在。
- 在我们的例子中,它被发现为第一个元素,返回值为 30。
- 获取密钥 vaibahv 的数据:
map.get(new Key("vaibhav"));
- 脚步:
- 计算 Key {“vaibhav”} 的哈希码。它将生成为 118。
- 使用指数法计算指数为6。
- 转到数组的索引 6 并将第一个元素的键与给定的键进行比较。如果两者相等则返回值,否则检查下一个元素是否存在。
- 在我们的例子中,它不是作为第一个元素找到的,节点对象的下一个不为空。
- 如果节点的下一个为空,则返回空。
- 如果节点的下一个不为空,则遍历第二个元素并重复过程3,直到未找到键或下一个不为空。
- put 和 get 方法的时间复杂度几乎是恒定的,直到重新散列未完成。
- 在发生冲突的情况下,即两个或多个节点的索引相同,节点通过链表连接,即第二个节点由第一个节点引用,第三个节点由第二个节点引用,依此类推。
- 如果给定的键已存在于 HashMap 中,则该值将替换为新值。
- 空键的哈希码为 0。
- 当获取带有键的对象时,将遍历链表,直到键匹配或在下一个字段中找到 null。