zoukankan      html  css  js  c++  java
  • Java中HashMap的内部处理

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

    1. int hash
    2. K key
    3. V value
    4. Node next

    现在我们来看看它是如何工作的。首先我们将看到hash的执行过程。

    Hashing

    哈希是使用hashCode()方法将对象转换为整数形式的过程。为了获得更好的HashMap性能,必须正确地编写hashCode()方法。这里我们自己定义一个Key类,这样我就可以重写hashCode()方法来显示不同的场景。我的key class:

    //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的第一个字符相同,hash代码也将是相同的。你不应该在你的实际代码中这样写。这只是为了演示。由于HashMap也允许空键,所以null的hash之是0。

    hashCode()方法

    方法用于获取对象的hashCode。Object类的hashCode()方法以整数形式返回对象的内存引用。在HashMap中,hashCode()用于计算bucket,从而计算索引。

    equals()方法

    equals方法用于检查两个对象是否相等。此方法由对象类提供。您可以在您的类中重写它以提供您自己的实现。
    HashMap使用equals()比较key是否相等。如果equals()方法返回true,则它们相等,否则不相等。

    buckets(哈希桶)

    bucket是HashMap数组的一个元素。它用于存储节点。两个或多个节点可以有同一个bucket。在这种情况下,链表结构用于连接节点。buckets与容量的关系如下:

    容量=bucket数量*负载系数

    一个bucket可以有多个节点,这取决于hashCode()方法。hashCode()方法越好,bucket的利用率就越高。

    HashMap中index的计算

    key的哈希值可能足够大以创建数组。生成的哈希值可能在整数的范围内,如果我们为这样一个范围创建数组,那么很容易导致outOfMemoryException。所以我们生成index来最小化数组的大小。执行以下操作来计算索引。

    index=hashCode(key)&(n-1)

    其中n是存储桶数或数组大小。在我们的示例中,我将考虑n作为默认大小,即16。

    初始化空的HashMap:这里,hashmap 大小我们取16。

    HashMap map = new HashMap();

    插入键值对:在上面的HashMap中放入一个键值对

    map.put(new Key("vishal"), 20);

    步骤:

    1. 计算键{“vishal”}的哈希code。将生成118。
    2. 计算index为6。
    3. 将节点对象创建为:
      {
        int hash = 118
      
        // {"vishal"} is not a string but 
        // an object of class Key
        Key key = {"vishal"}
      
        Integer value = 20
        Node next = null
      }
    4. 如果没有其他对象,则将此对象放在索引6处。

    插入另一个键值对:

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

    步骤:

    1. 计算键{“sachin”}的哈希码。它将生成为115。
    2. 计算index为3。
    3. 将节点对象创建为:
      {
        int hash = 115
        Key key = {"sachin"}
        Integer value = 30
        Node next = null
      }

    如果没有其他对象出现,则将此对象放在索引3处。

     发生碰撞时:现在,再放键值对,

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

    步骤:

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

    使用get()方法

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

    获取key为sachin的数据:
    map.get(new Key("sachin"));

    步骤

          1. 计算密钥{“sachin”}的哈希代码。它将生成为115。
          2. 计算index为3。
          3. 转到数组的索引3,比较第一个元素的键和给定的键。如果两者都等于,则返回值,否则检查下一个元素(如果存在)。
          4. 在我们的例子中,它作为第一个元素被找到,返回值是30。
    map.get(new Key("vaibhav"));

    步骤

        1. 计算密钥{“vaibhav”}的哈希码。将生成118。
        2. 计算index为6。
        3. 转到数组的索引6,比较第一个元素的键和给定的键。如果两者都等于,则返回值,否则检查下一个元素(如果存在)。
        4. 在我们的例子中,它不是作为第一个元素找到的,而node对象的下一个元素不为null。
        5. 如果node下个元素为null,则返回null。
        6. 如果node下个不为null,则遍历到第二个元素并重复过程3,直到找不到key或next不为nul
     

    完整代码

    // 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

    Java8中的HashMap的增强

    我们现在知道,在哈希冲突的情况下,对象作为节点存储在链表和equals()方法用于比较键。在链表中找到正确的key是一个线性操作,因此在最坏的情况下,复杂性变成O(n)。
    为了解决这个问题,java8散列元素在达到某个阈值后使用平衡树而不是链表。这意味着HashMap从在链表中存储条目对象开始,但是当散列中的项数大于某个阈值时,哈希将从使用链表变为平衡树,从而将最坏情况下的性能从O(n)提高到O(log n)。

    注意点

      1. 在没有扩容之前,对于put()和get()方法,时间复杂度几乎是恒定的。
      2. 当发生冲突时,即两个或多个节点的索引相同,节点通过链表连接,即第二个节点被第一个节点引用,第三个节点被第二个节点引用,依此类推。
      3. 如果给定的键已经存在于HashMap中,则该值将替换为新值。
      4. key是null的哈希值为0。
      5. 当获取带有键的对象时,将遍历链表,直到键匹配或在下一个字段中找到null为止。
  • 相关阅读:
    URL传参到servlet含特殊字符——#号无法传递
    textarea标签中间出现空格问题
    MySQL学习笔记(23)——自定义函数
    修改日期插件问题
    获取Spring的ApplicationContext的方法
    数据库用户被锁
    ava获得当前文件路径
    前端框架
    LeetCode 485. 最大连续1的个数
    LeetCode 283. 移动零
  • 原文地址:https://www.cnblogs.com/crelle/p/13657716.html
Copyright © 2011-2022 走看看