HashMap(3) 與HashTable區別

今天看代碼,想到去年發生的HashMap發生的CPU使用率100%的事件,轉載下當時看的三個比較不錯的博客(很是推薦)html

參考:http://coolshell.cn/articles/9606.htmljava

     http://github.thinkingbar.com/hashmap-analysis/git

   http://developer.51cto.com/art/201102/246431.htm程序員

 

 

 

人物:github

王小胖:性別:男。程序員,工做經驗1 year。愛好:吃肉、電玩、馬小花。特技:吃肉不用考慮胃的容量。算法

馬小花:性別:女。學生,工做經驗0 year。愛好:蛋糕、臭美、王小胖。特技:可以降服王小胖……shell

/**2011年2月,電影《將愛情進行到底》火得不得了。週末,小胖也陪着小花去看這部電影。放映中,小花被影片中的靖哥哥和杜拉拉感動的一沓糊塗,而小胖則內心暗自後悔沒有買一袋大爆米花來打發這無聊的時間。影片結束,小花已是鼻涕一把淚一把,小胖也只有裝模做樣地抽動了幾下鼻子,一心只想着一下子是吃麥當勞仍是必勝客。*/數組

回到家中,小胖和小花各自玩着電腦。多線程

小花:胖子,你知道Hashtable和HashMap的區別嗎?併發

小胖:略知。

小花:……裝什麼!!給我講講!!!

小胖:好的……

第一個區別就先來講說繼承關係吧。

若是你在baidu裏google一下(技術類文章的搜索仍是推薦google),會發現網上的大體說法與「因爲Java發展的歷史緣由。Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現。」相同。這種說法沒有錯,可是胖子以爲不夠準確,特別是對於咱們這種大衆菜鳥來講,若是不去深究的話,可能就會形成一些理解上的差別。簡單的認爲Hashtable沒有繼承Map接口。胖子以前就犯過這樣的錯誤(胖子認可本身笨,是真笨……)。

小花:那你怎麼知道它們兩個各自的繼承關係呢?胖子。

咱們能夠參考一下最新的JDK1.6的源碼,看看這兩個類的定義:

Java代碼

 

  1. public class Hashtable<k,v>     
  2.     extends Dictionary<k,v>     
  3. implements Map<k,v>, Cloneable, java.io.Serializable {…}     
  4.       
  5. public class HashMap<k,v>     
  6.     extends AbstractMap<k,v>     
  7. implements Map<k,v>, Cloneable, Serializable {…}   

能夠看到hashtable也是繼承了Map接口。它們的不一樣是Hashtable(since JDK1.0)就繼承了Dictionary這個抽象類,而HashMap(since JDK1.2)繼承的則是AbstractMap這個抽象類。由於在Hashtable中看到繼承Map後所實現的方法是JDK1.2版本時加上去的,因此胖子猜測多是在JDK 1.2開發時Sun工程師出於統一的考慮使得Hashtable也繼承了Map接口。

小花:哦,原來JDK源碼還能看出來這個。

小胖:……後面還能看出更多東西的。

小花:好期待啊。

第二個區別咱們從同步和併發性上來講說它們兩個的不一樣。

能夠經過這兩個類得源碼來分析,Hashtable中的主要方法都作了同步處理,而HashMap則沒有。能夠說Hashtable在默認狀況支持同步,而HashMap在默認狀況下是不支持的。咱們在多線程併發的環境下,能夠直接使用Hashtable,可是要使用HashMap的話就要本身增長同步處理了。對HashMap的同步處理可使用Collections類提供的synchronizedMap靜態方法;或者直接使用JDK5.0以後提供的java.util.concurrent包裏的ConcurrentHashMap類。

小胖:synchronizedMap靜態方法和ConcurrentHashMap類我會之後再給你詳細講一下的。肥婆。

小花:你保證啊。鑰匙忘了你知道後果的。

小胖:好的……

第三個區別就是它們對於null值的處理方式了。

咱們依然可以從源代碼中得知,Hashtable中,key和value都不容許出現null值。

Java代碼

 

  1. public synchronized V put(K key, V value) {     
  2.    // Make sure the value is not null     
  3.    if (value == null) {     
  4.        throw new NullPointerException();     
  5.    }     
  6.       
  7.    // Makes sure the key is not already in the hashtable.     
  8.    Entry tab[] = table;     
  9.    int hash = key.hashCode();     
  10.    int index = (hash & 0x7FFFFFFF) % tab.length;     
  11.    //…     
  12. }   

在咱們使用上面的方法時,如參數value爲null,能夠從代碼中直接看出程序會拋出NullPointerException;而在key爲null時,則會在「int hash = key.hashCode();「這段計算Hash值的過程當中拋出NullPointerException。而在在HashMap中,容許null做爲key存在,而且和其餘key的特性同樣,這樣的null值key只能有一個;另外HashMap容許多個value爲null。這樣你們就要注意了, HashMap中就不能用get(key)方法來判斷HashMap中是否存在某個key,由於value爲null和不存在該key的Entry都會返回null值,而應該用containsKey()方法來判斷了。(詳見源碼剖析)

Java代碼

 

  1. import java.util.HashMap;     
  2. import java.util.Map;     
  3. import java.util.Map.Entry;     
  4.       
  5. public class TestCase {     
  6.       
  7.     public static void main(String[] args) {     
  8.        Map<integer,string> hashMap = new HashMap<integer,string>();     
  9.        hashMap.put(0, null);     
  10.        hashMap.put(1, "one");     
  11.        hashMap.put(2, "two");     
  12.        hashMap.put(null, "null");     
  13.        for(Entry<integer, string> e : hashMap.entrySet()) {     
  14.            System.out.println("Key: " + e.getKey() + " -- Value: " + e.getValue());     
  15.        }     
  16.        System.out.println(hashMap.get(0));     
  17.        System.out.println(hashMap.get(4));     
  18.        System.out.println("Contains key 0 ? :" + hashMap.containsKey(0));     
  19.        System.out.println("Contains key 4 ? :" + hashMap.containsKey(4));     
  20.        System.out.println("Contains value null ? :" + hashMap.containsValue(null));     
  21.     }     
  22.       
  23. }  

結果:

Java代碼

 

  1. Key: null -- Value: null    
  2. Key: 0 -- Value: null    
  3. Key: 1 -- Value: one     
  4. Key: 2 -- Value: two     
  5. null    
  6. null    
  7. Contains key 0 ? :true    
  8. Contains key 4 ? :false    
  9. Contains value null ? :true   

HashMap對於null值key的處理網上有說「null 用new Object()來代替,其Entry.hashCode=0,並且在取出的時候還會還回null的。」胖子我在讀取源碼的過程當中看到了null值的hash值確實是0 (內部實現的數組中的index也是),可是能力有限沒有看到轉爲new Object()的過程。

小花: 原來hashMap的containsKey還有這麼個陷阱,之後肥婆要當心了。

第四個不一樣就是它們兩個Hash值的獲取方式了。

仍是經過源代碼源代碼,Hashtable是直接使用key對象的hash值。

Java代碼

 

  1. public synchronized V put(K key, V value) {     
  2.        // Make sure the value is not null     
  3.        if (value == null) {     
  4.            throw new NullPointerException();     
  5.        }     
  6.       
  7.        // Makes sure the key is not already in the hashtable.     
  8.        Entry tab[] = table;     
  9.        int hash = key.hashCode();//hashcode     
  10.        int index = (hash & 0x7FFFFFFF) % tab.length;     
  11.        //…     
  12. }   

而HashMap則是利用key對象的hash值從新計算一個新的hash值。

Java代碼

 

  1. public V put(K key, V value) {     
  2.         if (key == null)     
  3.             return putForNullKey(value);     
  4.         int hash = hash(key.hashCode());//hashcode     
  5.         int i = indexFor(hash, table.length);     
  6.         //…     
  7. }     
  8.       
  9. static int hash(int h) {     
  10.         h ^= (h >>> 20) ^ (h >>> 12);     
  11.         return h ^ (h >>> 7) ^ (h >>> 4);     
  12.     }  

小花:胖子,都用了hash算法,你給我講講Hash算法吧。

小胖:嗯……之後的,今天我比較忙(實際上是不會)。

小花:你是否是不會啊?嘿嘿(壞笑)。

小胖:什麼不會……談下一話題……

第五個不一樣就是Hashtable和HashMap它們兩個內部實現方式的數組的初始大小和擴容的方式。

HashMap中內部數組的初始容量是16, 加載因子爲0.75,並且數組容量增容後也要是2指數次冪:

Java代碼

 

  1. /**    
  2.      * The default initial capacity - MUST be a power of two.    
  3.      */    
  4. static final int DEFAULT_INITIAL_CAPACITY = 16;     
  5. /**    
  6.      * The load factor used when none specified in constructor.    
  7.      */    
  8.     static final float DEFAULT_LOAD_FACTOR = 0.75f; 

HashTable中的內部數組的初始容量是11,加載因子也是0.75數組的增容方式爲(oldCapacity * 2 + 1):

Java代碼

 

  1. public Hashtable() {     
  2.        this(11, 0.75f);     
  3.  }     
  4.       
  5. protected void rehash() {     
  6.        int oldCapacity = table.length;     
  7.        Entry[] oldMap = table;     
  8.       
  9.        int newCapacity = oldCapacity * 2 + 1;     
  10.        //…     
  11. }   

第六個不一樣咱們從它們兩個遍歷方式的內部實現上來講。

Hashtable HashMap都使用了 Iterator。而因爲歷史緣由,Hashtable還使用了Enumeration的方式 。

小花:Iterator和Enumeration的區別是什麼啊?給我講講。

小胖:我不是說我沒有時間嘛,下回的。

小花:我都記下來,免得你給我混過去。(拿起筆開始記帳中)

小胖:……(緊張)

第七個不一樣時它們的拷貝構造函數的不一樣。

依然是經過查看源碼,能夠發現它們兩個對於拷貝函數初始容量的不一樣值。

HashMap的實現是:

Java代碼

 

  1. public HashMap(Mapextends K, ? extends V> m) {     
  2.         this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,     
  3.                       DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);     
  4.         putAllForCreate(m);     
  5.     }   

而Hashtable的實現是:

Java代碼

 

  1. public Hashtable(Mapextends K, ? extends V> t) {     
  2.        this(Math.max(2*t.size(), 11), 0.75f);     
  3.        putAll(t);     
  4.     }   

小胖:今天講的已經不少了。我有點餓了,肥婆。

小花:看你今天的表現這麼好。走,帶你去吃烤肉去。

小胖:哈哈,肥婆萬歲。

相關文章
相關標籤/搜索