HashMap使用經驗(上)

小時候媽媽都教過我們,不同的東西要放在不同的位置,需要時才能快速找到它。當然這個規則你必須記住,不然怎麼都找不到了。HashMap之所以能夠做到快速存、取,與我們下面要介紹的內容密切相關。

HashMap和HashSet是JavaCollection Framework的兩個重要成員,其中HashMap是Map接口的常用實現類,HashSet是Set接口的常用實現類。雖然HashMap和HashSet實現的接口規範不同,但它們底層的Hash存儲機制完全一樣,甚至HashSet本身就採用HashMap來實現的。讀者請注意,雖然集合號稱存儲的是Java對象,但實際上並不會真正將Java對象放入Set集合中,只是在Set集合中保留這些對象的引用而言。也就是說:Java集合實際上是多個引用變量所組成的集合,這些引用變量指向實際的Java對象。就像引用類型的數組一樣,當我們把Java對象放入數組之時,並不是真正把Java對象放入數組中,只是把對象的引用放入數組中,每個數組元素都是一個引用變量。

我們首先來談談HashMap。HashMap是基於哈希表的Map接口的實現,HashMap將Hash值映射到內存地址,直接取得Key所對應的數據。在HashMap中,底層數據結構使用的是數組,所謂的內存地址即數組的下標索引。總的來說,除了非同步和允許使用Null之外,HashMap類與Hashtable大致相同。

HashMap實際上是一個鏈表的數組。基於HashMap的鏈表方式實現機制,只要hashCode()和hash()方法實現得足夠好,能夠儘可能地減少衝突的產生,那麼對HashMap的操作幾乎等價於對數組的隨機訪問操作,具有很好的性能。但是,如果hashCode()或者hash()方法實現較差,在大量衝突產生的情況下,HashMap事實上就退化爲幾個鏈表,對HashMap的操作等價於遍歷鏈表,此時性能很差。

HashMap的一個功能缺點是它的無序性,即被存入到HashMap中的元素,在遍歷HashMap時,其輸出是無序的。如果希望元素保持輸入的順序,可以使用LinkedHashMap替代。

HashMap採用一種所謂的“Hash算法”來決定每個元素的存儲位置。

當程序試圖將多個key-value放入HashMap中時,如代碼清單3-45所示。

代碼清單3-45 HashMap初始化示例

HashMap<String , Double> map = new HashMap<String ,Double>();

map.put("xiao ming" , 80.0);

map.put("xiao hong" , 89.0);

map.put("xiaohua" , 78.2);

當程序執行map.put("xiao ming" ,80.0);時,系統將調用"xiao ming"的hashCode()方法得到其hashCode值——每個Java對象都有hashCode()方法,都可通過該方法獲得它的hashCode值。當我們得到這個對象的hashCode值之後,系統會根據該hashCode值來決定該元素的存儲位置。

我們來看一下HashMap源代碼中的put(K key,V value)方式,代碼如清單3-46所示。

代碼清單3-46 HashMap源代碼

public V put(K key, V value)   

{   

 // 如果 key 爲 null,調用putForNullKey 方法進行處理

 if (key == null)   

     returnputForNullKey(value);   

 // 根據 key 的 keyCode 計算 Hash 值

 int hash =hash(key.hashCode());   

 // 搜索指定 hash 值在對應 table 中的索引

     int i = indexFor(hash,table.length);  

 // 如果 i 索引處的 Entry 不爲 null,通過循環不斷遍歷 e 元素的下一個元素

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

 {   

     Object k;   

     // 找到指定 key 與需要放入的 key 相等(hash 值相同

     // 通過 equals 比較放回 true)

     if (e.hash == hash&& ((k = e.key) == key   

         ||key.equals(k)))   

     {   

         V oldValue =e.value;   

         e.value = value;   

        e.recordAccess(this);   

         return oldValue;   

     }   

 }   

 // 如果 i 索引處的 Entry 爲 null,表明此處還沒有 Entry   

 modCount++;   

 // 將 key、value 添加到 i 索引處

 addEntry(hash, key, value,i);   

 return null;   

}  

清單3-46代碼調用了一個hash方法用於生成hash值,它是一個純粹的數學方法,源代碼如清單3-47所示。

代碼清單3-47 HashMap的hash方法源代碼

static int hash(int h)

{   

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

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

}


感興趣的朋友,可以掃二維碼關注 麥克叔叔每晚十點說,一起交流討論。

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