HashMap对HashCode碰撞的处理

来源:互联网 发布:linux的版本 编辑:程序博客网 时间:2024/06/03 21:49

先说Java之外的,什么是拉链法?怎么解决冲突的:

拉链法解决冲突的做法是:将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组t[0..m-1]。凡是散列地址为i的结点,均插入到以t为头指针的单链表中。t中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于1,但一般均取α≤1。

换句话说:HashCode是使用Key通过Hash函数计算出来的,由于不同的Key,通过此Hash函数可能会算的同样的HashCode,所以此时用了拉链法解决冲突,把HashCode相同的Value连成链表. 但是get的时候根据Key又去桶里找,如果是链表说明是冲突的,此时还需要检测Key是否相同





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

[java] view plain copy
  1. /** 
  2.      * Returns the value to which the specified key is mapped, 
  3.      * or if this map contains no mapping for the key. 
  4.      * 
  5.      * 获取key对应的value 
  6.      */  
  7.     public V get(Object key) {  
  8.         if (key == null)  
  9.             return getForNullKey();  
  10.     //获取key的hash值  
  11.         int hash = hash(key.hashCode());  
  12.     // 在“该hash值对应的链表”上查找“键值等于key”的元素  
  13.         for (Entry<K,V> e = table[indexFor(hash, table.length)];  
  14.              e != null;  
  15.              e = e.next) {  
  16.             Object k;  
  17.             if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
  18.                 return e.value;  
  19.         }  
  20.         return null;  
  21.     }  
  22.   
  23.     /** 
  24.      * Offloaded version of get() to look up null keys.  Null keys map 
  25.      * to index 0.   
  26.      * 获取key为null的键值对,HashMap将此键值对存储到table[0]的位置 
  27.      */  
  28.     private V getForNullKey() {  
  29.         for (Entry<K,V> e = table[0]; e != null; e = e.next) {  
  30.             if (e.key == null)  
  31.                 return e.value;  
  32.         }  
  33.         return null;  
  34.     }  
  35.   
  36.     /** 
  37.      * Returns <tt>true</tt> if this map contains a mapping for the 
  38.      * specified key. 
  39.      * 
  40.      * HashMap是否包含key 
  41.      */  
  42.     public boolean containsKey(Object key) {  
  43.         return getEntry(key) != null;  
  44.     }  
  45.   
  46.     /** 
  47.      * Returns the entry associated with the specified key in the 
  48.      * HashMap.   
  49.      * 返回键为key的键值对 
  50.      */  
  51.     final Entry<K,V> getEntry(Object key) {  
  52.         //先获取哈希值。如果key为null,hash = 0;这是因为key为null的键值对存储在table[0]的位置。  
  53.         int hash = (key == null) ? 0 : hash(key.hashCode());  
  54.         //在该哈希值对应的链表上查找键值与key相等的元素。  
  55.         for (Entry<K,V> e = table[indexFor(hash, table.length)];  
  56.              e != null;  
  57.              e = e.next) {  
  58.             Object k;  
  59.             if (e.hash == hash &&  
  60.                 ((k = e.key) == key || (key != null && key.equals(k))))  
  61.                 return e;  
  62.         }  
  63.         return null;  
  64.     }  
  65.   
  66.   
  67.     /** 
  68.      * Associates the specified value with the specified key in this map. 
  69.      * If the map previously contained a mapping for the key, the old 
  70.      * value is replaced. 
  71.      * 
  72.      * 将“key-value”添加到HashMap中,如果hashMap中包含了key,那么原来的值将会被新值取代 
  73.      */  
  74.     public V put(K key, V value) {  
  75.     //如果key是null,那么调用putForNullKey(),将该键值对添加到table[0]中  
  76.         if (key == null)  
  77.             return putForNullKey(value);  
  78.     //如果key不为null,则计算key的哈希值,然后将其添加到哈希值对应的链表中  
  79.         int hash = hash(key.hashCode());  
  80.         int i = indexFor(hash, table.length);  
  81.         for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  82.             Object k;  
  83.     //如果这个key对应的键值对已经存在,就用新的value代替老的value。  
  84.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  85.                 V oldValue = e.value;  
  86.                 e.value = value;  
  87.                 e.recordAccess(this);  
  88.                 return oldValue;  
  89.             }  
  90.         }  
  91.   
  92.         modCount++;  
  93.         addEntry(hash, key, value, i);  
  94.         return null;  
  95.     }  

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

[java] view plain copy
  1. // Entry是单向链表。  
  2.    // 它是 “HashMap链式存储法”对应的链表。  
  3.    // 它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数  
  4.    static class Entry<K,V> implements Map.Entry<K,V> {  
  5.        final K key;  
  6.        V value;  
  7. //指向下一个节点  
  8.        Entry<K,V> next;  
  9.        final int hash;  
  10.   
  11.        /** 
  12.         * Creates new entry. 
  13. * 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)" 
  14.         */  
  15.        Entry(int h, K k, V v, Entry<K,V> n) {  
  16.            value = v;  
  17.            next = n;  
  18.            key = k;  
  19.            hash = h;  
  20.        }  
  21.   
  22.        public final K getKey() {  
  23.            return key;  
  24.        }  
  25.   
  26.        public final V getValue() {  
  27.            return value;  
  28.        }  
  29.   
  30.        public final V setValue(V newValue) {  
  31.     V oldValue = value;  
  32.            value = newValue;  
  33.            return oldValue;  
  34.        }  
  35.       
  36.        // 判断两个Entry是否相等  
  37.        // 若两个Entry的“key”和“value”都相等,则返回true。  
  38.        // 否则,返回false  
  39.        public final boolean equals(Object o) {  
  40.            if (!(o instanceof Map.Entry))  
  41.                return false;  
  42.            Map.Entry e = (Map.Entry)o;  
  43.            Object k1 = getKey();  
  44.            Object k2 = e.getKey();  
  45.            if (k1 == k2 || (k1 != null && k1.equals(k2))) {  
  46.                Object v1 = getValue();  
  47.                Object v2 = e.getValue();  
  48.                if (v1 == v2 || (v1 != null && v1.equals(v2)))  
  49.                    return true;  
  50.            }  
  51.            return false;  
  52.        }  
  53.   
  54.        public final int hashCode() {  
  55.            return (key==null   ? 0 : key.hashCode()) ^  
  56.                   (value==null ? 0 : value.hashCode());  
  57.        }  
  58.   
  59.        public final String toString() {  
  60.            return getKey() + "=" + getValue();  
  61.        }  
  62.   
  63.        /** 
  64.         * This method is invoked whenever the value in an entry is 
  65.         * overwritten by an invocation of put(k,v) for a key k that's already 
  66.         * in the HashMap. 
  67.         */  
  68.        void recordAccess(HashMap<K,V> m) {  
  69.        }  
  70.   
  71.        /** 
  72.         * This method is invoked whenever the entry is 
  73.         * removed from the table. 
  74.         */  
  75.        void recordRemoval(HashMap<K,V> m) {  
  76.        }  
  77.    }  

从这段代码中,我们可以看出Entry是一个单向链表,这也是我们为什么说HashMap是通过拉链法解决hash冲突的原因。Entry实现了Map.Entry接口。
原创粉丝点击