zoukankan      html  css  js  c++  java
  • HashMap碰撞问题

    HashMap是最常用的集合类框架之一,它实现了Map接口,所以存储的元素也是键值对映射的结构,并允许使用null值和null键,其内元素是无序的,如果要保证有序,可以使用LinkedHashMap。HashMap是线程不安全的,下篇文章会讨论。HashMap的类关系如下:

        java.util 

        Class HashMap<K,V>

          java.lang.Object

              |--java.util.AbstractMap<K,V>

                    |--java.util.HashMap<K,V>

    所有已实现的接口:

      Serializable,Cloneable,Map<K,V>

    直接已知子类:

      LinkedHashMap,PrinterStateReasons

      HashMap中用的最多的方法就属put() 和 get() 方法;HashMap的Key值是唯一的,那如何保证唯一性呢?我们首先想到的是用equals比较,没错,这样可以实现,但随着内部元素的增多,put和get的效率将越来越低,这里的时间复杂度是O(n),假如有1000个元素,put时最差情况需要比较1000次。实际上,HashMap很少会用到equals方法,因为其内通过一个哈希表管理所有元素,哈希是通过hash单词音译过来的,也可以称为散列表,哈希算法可以快速的存取元素,当我们调用put存值时,HashMap首先会调用Key的hash方法,计算出哈希码,通过哈希码快速找到某个存放位置(桶),这个位置可以被称之为bucketIndex,但可能会存在多个元素找到了相同的bucketIndex,有个专业名词叫碰撞,当碰撞发生时,这时会取到bucketIndex位置已存储的元素,最终通过equals来比较,equals方法就是碰撞时才会执行的方法,所以前面说HashMap很少会用到equals。HashMap通过hashCode和equals最终判断出Key是否已存在,如果已存在,则使用新Value值替换旧Value值,并返回旧Value值,如果不存在 ,则存放新的键值对<K, V>到bucketIndex位置。通过下面的流程图来梳理一下整个put过程。

               

    最终HashMap的存储结构会有这三种情况,我们当然期望情形3是最少发生的(效率最低)。

    HashMap 碰撞问题处理:

      碰撞:所谓“碰撞”就上面所述是多个元素计算得出相同的hashCode,在put时出现冲突。

      处理方法:

      Java中HashMap是利用“拉链法”处理HashCode的碰撞问题。在调用HashMap的put方法或get方法时,都会首先调用hashcode方法,去查找相关的key,当有冲突时,再调用equals方法。hashMap基于hasing原理,我们通过put和get方法存取对象。当我们将键值对传递给put方法时,他调用键对象的hashCode()方法来计算hashCode,然后找到bucket(哈希桶)位置来存储对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当碰撞发生了,对象将会存储在链表的下一个节点中。hashMap在每个链表节点存储键值对对象。当两个不同的键却有相同的hashCode时,他们会存储在同一个bucket位置的链表中。键对象的equals()来找到键值对。

     HashMap基本结构概念图:

          

    到目前为止,我们了解了两件事:

      1、HashMap通过键的hashCode来快速的存取元素。

      2、当不同的对象发生碰撞时,HashMap通过单链表来解决,将新元素加入链表表头,通过next指向原有的元素。单链表在Java中的实现就是对象的引用(复合)。

     HashMap.put()和get()源码:

    /** 
     * Returns the value to which the specified key is mapped, 
     * or if this map contains no mapping for the key. 
     * 
     * 获取key对应的value 
     */  
    public V get(Object key) {  
        if (key == null)  
            return getForNullKey();  
        //获取key的hash值  
        int hash = hash(key.hashCode());  
        // 在“该hash值对应的链表”上查找“键值等于key”的元素  
        for (Entry<K,V> e = table[indexFor(hash, table.length)];  
             e != null;  
             e = e.next) {  
            Object k;  
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
                return e.value;  
        }  
        return null;  
    }  
    
    /** 
     * Offloaded version of get() to look up null keys.  Null keys map 
     * to index 0.   
     * 获取key为null的键值对,HashMap将此键值对存储到table[0]的位置 
     */  
    private V getForNullKey() {  
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {  
            if (e.key == null)  
                return e.value;  
        }  
        return null;  
    }  
    
    /** 
     * Returns <tt>true</tt> if this map contains a mapping for the 
     * specified key. 
     * 
     * HashMap是否包含key 
     */  
    public boolean containsKey(Object key) {  
        return getEntry(key) != null;  
    }  
    
    /** 
     * Returns the entry associated with the specified key in the 
     * HashMap.   
     * 返回键为key的键值对 
     */  
    final Entry<K,V> getEntry(Object key) {  
        //先获取哈希值。如果key为null,hash = 0;这是因为key为null的键值对存储在table[0]的位置。  
        int hash = (key == null) ? 0 : hash(key.hashCode());  
        //在该哈希值对应的链表上查找键值与key相等的元素。  
        for (Entry<K,V> e = table[indexFor(hash, table.length)];  
             e != null;  
             e = e.next) {  
            Object k;  
            if (e.hash == hash &&  
                ((k = e.key) == key || (key != null && key.equals(k))))  
                return e;  
        }  
        return null;  
    }  
    
    
    /** 
     * Associates the specified value with the specified key in this map. 
     * If the map previously contained a mapping for the key, the old 
     * value is replaced. 
     * 
     * 将“key-value”添加到HashMap中,如果hashMap中包含了key,那么原来的值将会被新值取代 
     */  
    public V put(K key, V value) {  
        //如果key是null,那么调用putForNullKey(),将该键值对添加到table[0]中  
        if (key == null)  
            return putForNullKey(value);  
        //如果key不为null,则计算key的哈希值,然后将其添加到哈希值对应的链表中  
        int hash = hash(key.hashCode());  
        int i = indexFor(hash, table.length);  
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
            Object k;  
            //如果这个key对应的键值对已经存在,就用新的value代替老的value。  
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
                V oldValue = e.value;  
                e.value = value;  
                e.recordAccess(this);  
                return oldValue;  
            }  
        }  
    
        modCount++;  
        addEntry(hash, key, value, i);  
        return null;  
    }

    从HashMap的put()和get方法实现中可以与拉链法解决hashCode冲突解决方法相互印证。并且从put方法中可以看出HashMap是使用Entry<K,V>来存储数据。

  • 相关阅读:
    JProfiler_SN_8_x key
    java格式化百分比
    获取每月第一天最后一天 java
    java 获取昨天日期
    eclipse git提交代码
    SIT与UAT的分别
    Spring <context:annotation-config/> 说明
    Hibernate日期映射类型
    Oracle查询备注信息
    Log4J入门
  • 原文地址:https://www.cnblogs.com/tongxuping/p/8276198.html
Copyright © 2011-2022 走看看