hashMap 和 hashTable 簡單介紹

以hashmap 源碼爲例, 分析map數據結構,


今天仔細看了一下hashmap的源碼,有hashmap又有了新的認識,先記錄下來,不敢妄談對hashm已經完全瞭解,但求能滴水穿石

無論hashtable 亦或 hashmap 存數map的基本數據結構都是  transient Entry[] table;

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
        ......
}
以前只是以爲,存儲的是一個 Entry 類型的數組,但是  請仔細看 Entry 的結構中有  這樣 一個 Entry<K,V> next;字段,沒錯 這是鏈表結構纔會有的 ,目的是爲了指向 下一個節點。

可是hashmap不是一個數組嗎?怎麼還扯出鏈表結構了呢?繼續看下面的代碼

    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;
    }
這是hashmap函數的put方法,下面我們來仔細分析一下這段代碼

首先 檢查   key  是否爲null,如果爲null ,則直接執行 putForNullKey函數 

    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;
    }
因爲null的hashcode始終爲0 ,所以 k = null v = valye的這個map一定放在  table的 第 0個 位置(entry 在table中 存放位置 稍後解釋)

注意紅色標記的那句代碼告訴我們,table中每個索引位 存放的就是一個鏈表。

好了,我們回到上面的put方法,接着往下看,

通過key的hashCode()方法計算了這個key的hash值,這個hash值被用來計算存儲Entry對象的數組中的位置。JDK的設計者假設會有一些人可能寫出非常差的hashCode()方法,會出現一些非常大或者非常小的hash值。爲了解決這個問題,他們引入了另外一個hash函數,接受對象的hashCode(),並轉換到適合數組的容量大小。

接着是indexFor(hash,table,length)方法,這個方法計算了entry對象存儲的準確位置。

接下來就是主要的部分,我們都知道兩個不相等的對象可能擁有過相同的hashCode值,兩個不同的對象是怎麼存儲在相同的索引位[叫做bucket]呢?

答案是LinkedList。如果你記得,Entry類有一個next變量,這個變量總是指向鏈中的下一個變量,這完全符合鏈表的特點。

所以,在發生碰撞的時候,entry對象會被以鏈表的形式存儲起來,當一個Entry對象需要被存儲的時候,hashmap檢查該位置是否已近有了一個entry對象,如果沒有就存在那裏,如果有了就檢查她的next屬性,如果是空,當前的entry對象就作爲已經存儲的entry對象的下一個節點,依次類推。

在這種方式下HashMap就能保證key的唯一性。


又上我們可以推斷出 get(key)函數  的 工作原理

先 得到 key  的 hashcode 。然後通過hash函數封裝hashcode得到  int型數據  hash

indexFor 函數 通過hash 得到  entry 在 table中的索引 ,然後開始遍歷 該索引位的 entry鏈表 (遍歷條件,hash值首先要相等,當然得了, key == 或者  equal )

條件符合 則範圍 entey.value

下面就是get函數(與上面的推斷一致)

    public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        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;
    }


http://pic002.cnblogs.com/images/2012/132622/2012082410402945.jpg

至此,OK,hashmap函數的分析先告一段落,上面的圖像,完完全全的體現出 table 數組存數的entry的存數結構。

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