HashMap 如何解決hash衝突

在Java編程語言中,最基本的結構就是兩種,一種是數組,一種是模擬指針(引用),全部的數據結構均可以用這兩個基本結構構造,HashMap也同樣。 當程序試圖將多個 key-value 放入 HashMap 中時,以以下代碼片斷爲例: java

HashMap<String,Object> m=new HashMap<String,Object>();
m.put("a", "rrr1");
m.put("b", "tt9");
m.put("c", "tt8");
m.put("d", "g7");
m.put("e", "d6");
m.put("f", "d4");
m.put("g", "d4");
m.put("h", "d3");
m.put("i", "d2");
m.put("j", "d1");
m.put("k", "1");
m.put("o", "2");
m.put("p", "3");
m.put("q", "4");
m.put("r", "5");
m.put("s", "6");
m.put("t", "7");
m.put("u", "8");
m.put("v", "9");算法

        HashMap 採用一種所謂的「Hash 算法」來決定每一個元素的存儲位置。當 程序執行 map.put(String,Obect)方法 時,系統將調用String的 hashCode() 方法獲得其 hashCode 值——每一個 Java 對象都有 hashCode() 方法,均可經過該方法得到它的 hashCode 值。獲得這個對象的 hashCode 值以後,系統會根據該 hashCode 值來決定該元素的存儲位置。源碼以下: 編程

Java代碼   收藏代碼數組

   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;  
            //判斷當前肯定的索引位置是否存在相同hashcode和相同key的元素,若是存在相同的hashcode和相同的key的元素,那麼新值覆蓋原來的舊值,並返回舊值。  
            //若是存在相同的hashcode,那麼他們肯定的索引位置就相同,這時判斷他們的key是否相同,若是不相同,這時就是產生了hash衝突。  
            //Hash衝突後,那麼HashMap的單個bucket裏存儲的不是一個 Entry,而是一個 Entry 鏈。  
            //系統只能必須按順序遍歷每一個 Entry,直到找到想搜索的 Entry 爲止——若是剛好要搜索的 Entry 位於該 Entry 鏈的最末端(該 Entry 是最先放入該 bucket 中),  
            //那系統必須循環到最後才能找到該元素。  
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
                V oldValue = e.value;  
                e.value = value;  
                return oldValue;  
            }  
        }  
        modCount++;  
        addEntry(hash, key, value, i);  
        return null;  
    }

       上面程序中用到了一個重要的內部接口:Map.Entry,每一個 Map.Entry 其實就是一個 key-value 對。從上面程序中能夠看出:當系統決定存儲 HashMap 中的 key-value 對時,徹底沒有考慮 Entry 中的 value,僅僅只是根據 key 來計算並決定每一個 Entry 的存儲位置。這也說明了前面的結論:咱們徹底能夠把 Map 集合中的 value 當成 key 的附屬,當系統決定了 key 的存儲位置以後,value 隨之保存在那裏便可.HashMap程序通過我改造,我故意的構造出了hash衝突現象,由於HashMap的初始大小16,可是我在hashmap裏面 放了超過16個元素,而且我屏蔽了它的resize()方法。不讓它去擴容。這時HashMap的底層數組Entry[]   table結構以下:  數據結構

   

       Hashmap裏面的bucket出現了單鏈表的形式,散列表要解決的一個問題就是散列值的衝突問題,一般是兩種方法:鏈表法和開放地址法。鏈表法就是將 相同hash值的對象組織成一個鏈表放在hash值對應的槽位;開放地址法是經過一個探測算法,當某個槽位已經被佔據的狀況下繼續查找下一個能夠使用的槽 位。java.util.HashMap採用的鏈表法的方式,鏈表是單向鏈表。造成單鏈表的核心代碼以下: 編程語言

 

Java代碼   收藏代碼 性能

 void addEntry(int hash, K key, V value, int bucketIndex) {  
        Entry<K,V> e = table[bucketIndex];  
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
        if (size++ >= threshold)  
            resize(2 * table.length);  
    }

     上面方法的代碼很簡單,但其中包含了一個設計:系統老是將新添加的 Entry 對象放入 table 數組的 bucketIndex 索引處——若是 bucketIndex 索引處已經有了一個 Entry 對象,那新添加的 Entry 對象指向原有的 Entry 對象(產生一個 Entry 鏈),若是 bucketIndex 索引處沒有 Entry 對象,也就是上面程序代碼的 e 變量是 null,也就是新放入的 Entry 對象指向 null,也就是沒有產生 Entry 鏈。
spa

       HashMap裏面沒有出現hash衝突時,沒有造成單鏈表時,hashmap查找元素很快,get()方法可以直接定位到元素,可是出現單鏈表後,單個 bucket 裏存儲的不是一個 Entry,而是一個 Entry 鏈,系統只能必須按順序遍歷每一個 Entry,直到找到想搜索的 Entry 爲止——若是剛好要搜索的 Entry 位於該 Entry 鏈的最末端(該 Entry 是最先放入該 bucket 中),那系統必須循環到最後才能找到該元素。 設計

       當建立 HashMap 時,有一個默認的負載因子(load factor),其默認值爲 0.75,這是時間和空間成本上一種折衷:增大負載因子能夠減小 Hash 表(就是那個 Entry 數組)所佔用的內存空間,但會增長查詢數據的時間開銷,而查詢是最頻繁的的操做(HashMap 的 get() 與 put() 方法都要用到查詢);減少負載因子會提升數據查詢的性能,但會增長 Hash 表所佔用的內存空間。 指針

相關文章
相關標籤/搜索