【轉載】HashSet與HashMap關係之源碼分析

題目:請說出hashCode方法,equals方法,HashSet,HasMap之間的關係?

解答:策略,分析jdk的源代碼:

  1. public HashSet() {   
  2. ap = new HashMap<E,Object>();   
  3.   }  

  public HashSet() {

  map = new HashMap<E,Object>();

    }

1、HashSet底層是採用HashMap實現的。

private transient HashMap<E,Object> map;是HashSet類裏面定義的一個私有的成員變量。並且是transient類型的,在序列化的時候是不會序列化到文件裏面去的,信息會丟失。HashMap<E,Object>裏面的key爲E,是HashSet<E>裏面放置的對象E(對象的引用,爲了簡單方便起見我說成是對象,一定要搞清楚,在集合裏面放置的永遠都是對象的引用,而不是對象,對象是在堆裏面的,這個一定要注意),HashMap<E,Object>的value是Object類型的。

2、這個HashMap的key就是放進HashSet中對象,value是Object類型的。當我去使用一個默認的構造方法的時候,執行public HashSet() {map = new HashMap<E,Object>();},會將HashMap實例化,將集合能容納的內型作爲key,Object作爲它的value。當我們去往HashSet裏面放值的時候,這個HashMap<E,Object>裏面的Object類型的value到底取什麼值呢?這個時候我們就需要看HashSet的add方法,因爲add方法可以往裏面增加對象,通過往裏面增加對象,會導致底層的HashMap增加一個key和value。這個時候我們看一下add方法:  public boolean add(E e) {return map.put(e, PRESENT)==null;},add方法接受一個E類型的參數,這個E類型就是我們在HashSet裏面能容納的類型,當我們往HashSet裏面add對象的時候,HashMap是往裏面put(E,PRESET),增加E爲key,PRESET爲value,PRESET是這樣定義的:private static final Object PRESENT = new Object();從這裏我們知道PRESET是private static final Object的常量並且已經實例化好了。HashMap<E,Object>()中的key爲HashSet中add的對象,不能重複,value爲PRESET,這是jdk爲了方便將value設爲常量PRESET,因爲value是可以重複的。

3、當調用HashSet的add方法時,實際上是向HashMap中增加了一行(key-value對),該行的key就是向HashSet增加的那個對象,該行的value就是一個Object類型的常量。

接着,我們看看HashMap的源碼,流程講到map.put(e, PRESENT)==null,我們往HashSet裏面add一個對象,那麼HashMap就往裏面put(key,value)。HashMap的put方法源碼如下:

  1. public V put(K key, V value) {   
  2.         if (key == null)   
  3.             return putForNullKey(value);   
  4.         int hash = hash(key.hashCode());   
  5.         int i = indexFor(hash, table.length);   
  6.         for (Entry<K,V> e = table[i]; e != null; e = e.next) {   
  7.             Object k;   
  8.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {   
  9.                 V oldValue = e.value;   
  10.                 e.value = value;   
  11.                 e.recordAccess(this);   
  12.                 return oldValue;   
  13.             }   
  14.         }   
  15.   
  16.         modCount++;   
  17.         addEntry(hash, key, value, i);   
  18.         return null;   
  19.     }  

public V put(K key, V value) {

        if (key == null)

            return putForNullKey(value);

        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;

            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;

    }

首先,如果key == null,這個key是往HashSet裏面add的那個對象,它返回一個putForNullKey(value),它是一個方法,源碼如下:

  1. private V putForNullKey(V value) {   
  2.        for (Entry<K,V> e = table[0]; e != null; e = e.next) {   
  3.            if (e.key == null) {   
  4.                V oldValue = e.value;   
  5.                e.value = value;   
  6.                e.recordAccess(this);   
  7.                return oldValue;   
  8.            }   
  9.        }   
  10.        modCount++;   
  11.        addEntry(0, null, value, 0);   
  12.        return null;   
  13.    }  

 private V putForNullKey(V value) {

        for (Entry<K,V> e = table[0]; e != null; e = e.next) {

            if (e.key == null) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }

        modCount++;

        addEntry(0, null, value, 0);

        return null;

    }

這裏面有一個Entry,我們看看它的源碼:

  1. static class Entry<K,V> implements Map.Entry<K,V> {   
  2.         final K key;   
  3.         V value;   
  4.         Entry<K,V> next;   
  5.         final int hash;   
  6.   
  7.         /**  
  8.          * Creates new entry.  
  9.          */  
  10.         Entry(int h, K k, V v, Entry<K,V> n) {   
  11.             value = v;   
  12.             next = n;   
  13.             key = k;   
  14.             hash = h;   
  15.         }   
  16.   
  17.         public final K getKey() {   
  18.             return key;   
  19.         }   
  20.   
  21.         public final V getValue() {   
  22.             return value;   
  23.         }   
  24.   
  25.         public final V setValue(V newValue) {   
  26.         V oldValue = value;   
  27.             value = newValue;   
  28.             return oldValue;   
  29.         }   
  30.   
  31.         public final boolean equals(Object o) {   
  32.             if (!(o instanceof Map.Entry))   
  33.                 return false;   
  34.             Map.Entry e = (Map.Entry)o;   
  35.             Object k1 = getKey();   
  36.             Object k2 = e.getKey();   
  37.             if (k1 == k2 || (k1 != null && k1.equals(k2))) {   
  38.                 Object v1 = getValue();   
  39.                 Object v2 = e.getValue();   
  40.                 if (v1 == v2 || (v1 != null && v1.equals(v2)))   
  41.                     return true;   
  42.             }   
  43.             return false;   
  44.         }   
  45.   
  46.         public final int hashCode() {   
  47.             return (key==null   ? 0 : key.hashCode()) ^   
  48.                    (value==null ? 0 : value.hashCode());   
  49.         }   
  50.   
  51.         public final String toString() {   
  52.             return getKey() + "=" + getValue();   
  53.         }   
  54.   
  55.         /**  
  56.          * This method is invoked whenever the value in an entry is  
  57.          * overwritten by an invocation of put(k,v) for a key k that's already  
  58.          * in the HashMap.  
  59.          */  
  60.         void recordAccess(HashMap<K,V> m) {   
  61.         }   
  62.   
  63.         /**  
  64.          * This method is invoked whenever the entry is  
  65.          * removed from the table.  
  66.          */  
  67.         void recordRemoval(HashMap<K,V> m) {   
  68.         }   
  69.     }  

static class Entry<K,V> implements Map.Entry<K,V> {

        final K key;

        V value;

        Entry<K,V> next;

        final int hash;

 

        /**

         * Creates new entry.

         */

        Entry(int h, K k, V v, Entry<K,V> n) {

            value = v;

            next = n;

            key = k;

            hash = h;

        }

        public final K getKey() {

            return key;

        }

        public final V getValue() {

            return value;

        }

        public final V setValue(V newValue) {

    V oldValue = value;

            value = newValue;

            return oldValue;

        }

        public final boolean equals(Object o) {

            if (!(o instanceof Map.Entry))

                return false;

            Map.Entry e = (Map.Entry)o;

            Object k1 = getKey();

            Object k2 = e.getKey();

            if (k1 == k2 || (k1 != null && k1.equals(k2))) {

                Object v1 = getValue();

                Object v2 = e.getValue();

                if (v1 == v2 || (v1 != null && v1.equals(v2)))

                    return true;

            }

            return false;

        }

        public final int hashCode() {

            return (key==null   ? 0 : key.hashCode()) ^

                   (value==null ? 0 : value.hashCode());

        }

        public final String toString() {

            return getKey() + "=" + getValue();

        }

        /**

         * This method is invoked whenever the value in an entry is

         * overwritten by an invocation of put(k,v) for a key k that's already

         * in the HashMap.

         */

        void recordAccess(HashMap<K,V> m) {

        }

        /**

         * This method is invoked whenever the entry is

         * removed from the table.

         */

        void recordRemoval(HashMap<K,V> m) {

        }

    }

static class Entry表示它是一個靜態的內部類,注意,外部類是不能用static的,對類來說,只有內部類能用static來修飾。這個Entry它實現了一個Map.Entry<K,V>接口,我看看Map.Entry<K,V>的源碼:

  1. interface Entry<K,V> {   
  2.     K getKey();   
  3.     V getValue();   
  4.     V setValue(V value);   
  5.     boolean equals(Object o);   
  6.     int hashCode();   
  7.     }  

interface Entry<K,V> {

K getKey();

V getValue();

V setValue(V value);

boolean equals(Object o);

int hashCode();

    }

Map.Entry<K,V>它是一個接口,裏面定義了幾個方法,Map.Entry<K,V>它實際上是一個內部的接口(inner interface),Map.Entry幹什麼用的呢?我看看Map接口有一個 Set<Map.Entry<K, V>> entrySet();方法,它返回一個Set集合,它裏面是Map.Entry<K, V>類型的,這個方法用得非常多。表示你在調用一個Map的entrySet()方法,它會返回一個Set集合,這個集合裏面放置的就是你的Map的key和value這兩個對象所共同組成的Map.Entry類型的那樣的對象,Map.Entry類型對象裏面放置的就是你的HashMap裏面的key和value,所以說當你去調用一個Map(不管是HashMap還是Treemap)的entrySet()方法,會返回一個Set集合,個集合裏面放置的就是你的Map的key和value這兩個對象所共同組成的Map.Entry類型的那樣的對象。

HashMap的底層是怎樣維護的呢?我們看一下源碼:

  1. /**  
  2.    * The table, resized as necessary. Length MUST Always be a power of two.  
  3.    */  
  4.   transient Entry[] table;  

  /**

     * The table, resized as necessary. Length MUST Always be a power of two.

     */

    transient Entry[] table;

它是一個Entry類型的數組,table數組裏面放Entry類型的,Entry的源碼有:

  1. final K key;   
  2.    V value;  

     final K key;

        V value;

這裏的K表示HashMap的key,V表示HashMap的value,所以我們可以大膽的斷定,HashMap的底層是用數組來維護的。理解這一點非常重要,因爲java的集合,底層大部分都是用數組來維護的,頂層之所以有那麼高級,是因爲底層對其進行了封裝。

4、HashMap底層採用數組來維護。數組裏面的每一個元素都是Entry,而這個Entry裏面是Key和value組成的內容。

我們看HashMap的put方法,如果key == null,返回putForNullKey(value),看putForNullKey方法:

Java代碼

  1. private V putForNullKey(V value) {   
  2.         for (Entry<K,V> e = table[0]; e != null; e = e.next) {   
  3.             if (e.key == null) {   
  4.                 V oldValue = e.value;   
  5.                 e.value = value;   
  6.                 e.recordAccess(this);   
  7.                 return oldValue;   
  8.             }   
  9.         }   
  10.         modCount++;   
  11.         addEntry(0, null, value, 0);   
  12.         return null;   
  13.     }  

private V putForNullKey(V value) {

        for (Entry<K,V> e = table[0]; e != null; e = e.next) {

            if (e.key == null) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }

        modCount++;

        addEntry(0, null, value, 0);

        return null;

    }

首先做一個for循環,從Entry數組的第一個元素開始,e.next是static class Entry<K,V> implements Map.Entry<K,V>的一個成員屬性 Entry<K,V> next;next也是Entry<K,V>類型的,next也可以指向Entry類型的對象,當我知道一個Entry類型的對象,就可以通過它的next屬性知道這個Entry類型的對象的後面一個Entry類型的對象,通過這個next變量來尋找下一個。next指向的Entry類型的對象不在數組中。

接着判斷e.key == null,如果e.key 爲null,說明HashMap裏面沒有這個對象,這個時就會將我們add到Set裏面的對象真真正正的放置到Entry數組裏面去。

我們在看HashMap的put方法裏面:int hash = hash(key.hashCode());如果key不爲null,這個key就是我們往HashSet裏面放置的那個對象。它就會調用key.hashCode()方法,這是流程轉到hashCode方法裏面去了,調用hashCode()方法返回hash碼,接着調用hash方法,我們看看hash方法源碼:

  1. static int hash(int h) {   
  2.         // This function ensures that hashCodes that differ only by   
  3.         // constant multiples at each bit position have a bounded   
  4.         // number of collisions (approximately 8 at default load factor).   
  5.         h ^= (h >>> 20) ^ (h >>> 12);   
  6.         return h ^ (h >>> 7) ^ (h >>> 4);   
  7.     }  

static int hash(int h) {

        // This function ensures that hashCodes that differ only by

        // constant multiples at each bit position have a bounded

        // number of collisions (approximately 8 at default load factor).

        h ^= (h >>> 20) ^ (h >>> 12);

        return h ^ (h >>> 7) ^ (h >>> 4);

    }

這個hash方法異常複雜。並且jdk5.0和jdk6.0對這個方法的實現方式也是不一樣的。hash方法就是我們在數據結構中講的散列函數。它是經過放進HashSet裏面的對象作爲key得到hashCode碼,在進行散列得到的一個整數。

接着執行put方法裏面的int i = indexFor(hash, table.length);語句,我們看看indexFor方法的源碼:

  1. * Returns index for hash code h.   
  2.     */   
  3.    static int indexFor(int h, int length) {   
  4.        return h & (length-1);   
  5.    }  

 * Returns index for hash code h.

     */

    static int indexFor(int h, int length) {

        return h & (length-1);

    }

indexFor方法也返回一個整型值,將上面通過hash方法得到的int值和數組的長度進行與運算,然後返回。他是往HashSet裏面放置對象位置的索引值,它的值永遠在0到數組長度減1之間,它是不會超過數組長度的。它是有hash方法和indexFor方法來保證不會超過的。

所以對於put方法,如果indexFor返回2,那麼就在數組的第2個位置,然後執行for循環,如果第2個位置沒有元素,則跳出for循環,調用addEntry方法將這個元素添加到數組中第2個位置,addEntry(hash, key, value, i);這是比較簡單的情況。比較複雜的情況,數組的第2個位置已經有元素了,不爲null,那麼它是怎麼做的呢?我們看一下:

  1. Object k;   
  2.           if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {   
  3.               V oldValue = e.value;   
  4.               e.value = value;   
  5.               e.recordAccess(this);   
  6.               return oldValue;   
  7.           }  

  Object k;

            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

它用這種方式方式進行比較,(e.hash == hash && ((k = e.key) == key || key.equals(k))它會醫用k的equals方法來個第2個位置上的元素進行比較,如果這個equals方法返回true,表示這個對象確實是一個對象,這個時候還沒有完,如果e.hash == hash並且key.equals(k)表示真的是相同的,這個時候就會用新的相同的對象的值替換就的那個值,同時它把舊的那個被替換的值給返回。如果不相同呢,它會根據next指向的對象再去比較,如果又不成功又會根據它的next鏈去尋找比較,一直到最後一個,當爲null的時候下一個就不存在了。這就是put方法的for過程。我們往HashMap裏面放值的時候,兩種情況,要麼放進去了增加(key-value)要麼替換掉(將舊的值替換掉了,其實是一樣的)了。如果都不成功,表示在鏈上不存在,這個時候就直接添加到數組,同時將添加的這個Entry的next執行往外替換的那個Entry,這樣做的好處就是減少索引時間。不管你鏈接有多長,每次查找時間固定(用hash方法)。  

5、調用增加的那個對象的hashCode方法,來得到一個hashCode值,然後根據該值來計算出一個數組的下表索引(計算出數組中的一個位置)

6、將準備增加到map中的對象與該位置上的對象進行比較(equals方法),如果相同,那麼就將該位置上的那個對象(Entry類型)的value值替換掉,否則沿着該Entry的鏈接繼承重複上述過程,如果到鏈的最後仍然沒有找到與此對象相同的對象,那麼這個時候就會將該對象增加到數組中,將數組中該位置上的那個Entry對象鏈到該對象的後面。

7、對於HashSet,HashMap來說,這樣做就是爲了提高查找的效率,使得查找時間不隨着Set或者Map的大小而改變。

轉自http://blog.163.com/zhangbingwu2006@126/blog/static/3111052120093924536946/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章