Java中HashMap的get() put() 内部工作(翻译)

申明:   本文章是翻译于国外一篇写的不错的文章,叙述了HashMap内部的工作,有少许的内容补充,若发现有错误的地方还请在评论区指出,谢谢

下面我们将看到的HashMap中的get() 方法put() 方法如何在内部工作。执行了哪些操作。如何进行哈希计算的。如何通过Key值获取Value值。如何存储key-value键值对。

HashMap中包含一个节点数组NodeArray,节点Node可以表示一个具有以下对象的类:

 

  1. int hash
  2. K key
  3. V value
  4. 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()方法以整数形式返回对象的内存引用.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);

 

步骤

  1. 计算Key {“vishal”}的哈希码。它将生成为118。
  2. 使用索引方法计算索引将为6。
  3. 创建节点对象:
{
  int hash = 118

  // {"vishal"} 代表的是一个类,而不是一个字符串
  Key key = {"vishal"}

  Integer value = 20
  Node next = null
}

 

  1. 如果此处没有其他对象,则将此对象放在索引6处。

现在的HashMap变成

 

插入另一个键值对:现在,把另一对,即:

map.put(new Key("sachin"), 30);

 

步骤

  1. 计算Key {“sachin”}的hashCode。它将生成为115。
  2. 使用索引方法计算索引将为3。
  3. 创建节点对象:
{
  int hash = 115
  Key key = {"sachin"}
  Integer value = 30
  Node next = null
}

 

 

如果此处没有其他对象,则将此对象放在索引3处。
现在HashMap中变成:

 

在碰撞的情况下:现在,再放一对:

map.put(new Key("vaibhav"), 40);

 

脚步:

  1. 计算Key {“vaibhav”}的哈希码。它将生成为118。
  2. 使用索引方法计算索引将为6。
  3. 创建节点对象:
 {
  int hash = 118
  Key key = {"vaibhav"}
  Integer value = 40
  Node next = null
}
  1. 如果此处没有其他对象,则将此对象放在索引6处。
  2. 在这种情况下,在索引6处找到节点对象 - 这是碰撞的情况。
  3. 在这种情况下,通过的hashCode()和equals()方法方法检查两个键是否相同。
  4. 如果键相同,将值替换为当前值。
  5. 否则,通过链表将此节点对象连接到上一个节点对象,并且两者都存储在索引6中。

    现在,HashMap中变为:

使用get方法()

 

 

现在让我们尝试一些获取方法来获取值.get(K key)方法用于通过其获取值。如果您不知道Key,则无法获取值。

获取关键萨钦的数据:

map.get(new Key("sachin"));

 

  • 步骤:

    1. 计算Key {“sachin”}的哈希码。它将生成为115。
    2. 使用索引方法计算索引将为3。
    3. 转到数组的索引3并将第一个元素的键与给定键进行比较。如果两者都是等于则返回值,否则检查下一个元素是否存在。
    4. 在我们的例子中,它被发现是第一个元素,返回值是30。
  • 获取Key vaibahv的数据:

 

map.get(new Key("vaibhav"));

 

 

步骤:

  1. 计算Key {“vaibhav”}的哈希码。它将生成为118。
  2. 使用索引方法计算索引将为6。
  3. 转到数组的索引6并将第一个元素的与给定键键进行比较。如果两者都是等于则返回值值,否则检查下一个元素是否存在。
  4. 在我们的例子中,它不是第一个元素,而节点对象的下一个元素不是空。
  5. 如果的下节点一个为空,则返回空值。
  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。
  • 获取具有其键的对象时,将遍历链接列表,直到键匹配或在下一个字段上找到空。

 如果您发现任何不正确的内容,或者您​​想要分享有关上述内容的更多信息,请撰写评论。

 

原文 请 点击  这里  

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值