JAVA中HashMap和Hashtable區別

轉載出處:https://www.cnblogs.com/lchzls/p/6714335.html

Hashtable和HashMap在Java面試中相當容易被問到,甚至成爲了集合框架面試題中最常被考的問題,所以在參加任何Java面試之前,都不要忘了準備這一題。

 

我們先看2個類的定義

public class Hashtable  
    extends Dictionary  
    implements Map, Cloneable, Java.io.Serializable  
public class HashMap  
    extends AbstractMap  
    implements Map, Cloneable, Serializable  

可見Hashtable 繼承自 Dictiionary 而 HashMap繼承自AbstractMap

Hashtable的put方法如下

複製代碼
public synchronized V put(K key, V value) {  //###### 注意這裏1  
  // Make sure the value is not null  
  if (value == null) { //###### 注意這裏 2  
    throw new NullPointerException();  
  }  
  // Makes sure the key is not already in the hashtable.  
  Entry tab[] = table;  
  int hash = key.hashCode(); //###### 注意這裏 3  
  int index = (hash & 0x7FFFFFFF) % tab.length;  
  for (Entry e = tab[index]; e != null; e = e.next) {  
    if ((e.hash == hash) && e.key.equals(key)) {  
      V old = e.value;  
      e.value = value;  
      return old;  
    }  
  }  
  modCount++;  
  if (count >= threshold) {  
    // Rehash the table if the threshold is exceeded  
    rehash();  
    tab = table;  
    index = (hash & 0x7FFFFFFF) % tab.length;  
  }  
  // Creates the new entry.  
  Entry e = tab[index];  
  tab[index] = new Entry(hash, key, value, e);  
  count++;  
  return null;  
}  
複製代碼

注意1 方法是同步的
注意2 方法不允許value==null
注意3 方法調用了key的hashCode方法,如果key==null,會拋出空指針異常

 

HashMap的put方法如下

複製代碼
public V put(K key, V value) { //###### 注意這裏 1  
  if (key == null)  //###### 注意這裏 2  
    return putForNullKey(value);  
  int hash = hash(key.hashCode());  
  int i = indexFor(hash, table.length);  
  for (Entry 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;  
}  
複製代碼

注意1 方法是非同步的
注意2 方法允許key==null
注意3 方法並沒有對value進行任何調用,所以允許爲null

 

是否提供contains方法

 HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因爲contains方法容易讓人引起誤解。

Hashtable則保留了contains,containsValue和containsKey三個方法,其中contains和containsValue功能相同。

我們看一下Hashtable的ContainsKey方法和ContainsValue的源碼:

public boolean containsValue(Object value) {      
     return contains(value);      
 }  
複製代碼
// 判斷Hashtable是否包含“值(value)”      
 public synchronized boolean contains(Object value) {      
     //注意,Hashtable中的value不能是null,      
     // 若是null的話,拋出異常!      
     if (value == null) {      
         throw new NullPointerException();      
     }      
    
     // 從後向前遍歷table數組中的元素(Entry)      
     // 對於每個Entry(單向鏈表),逐個遍歷,判斷節點的值是否等於value      
     Entry tab[] = table;      
     for (int i = tab.length ; i-- > 0 ;) {      
         for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {      
             if (e.value.equals(value)) {      
                 return true;      
             }      
         }      
     }      
     return false;      
 }  
複製代碼
複製代碼
// 判斷Hashtable是否包含key      
 public synchronized boolean containsKey(Object key) {      
     Entry tab[] = table;      
/計算hash值,直接用key的hashCode代替    
     int hash = key.hashCode();        
     // 計算在數組中的索引值     
     int index = (hash & 0x7FFFFFFF) % tab.length;      
     // 找到“key對應的Entry(鏈表)”,然後在鏈表中找出“哈希值”和“鍵值”與key都相等的元素      
     for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {      
         if ((e.hash == hash) && e.key.equals(key)) {      
             return true;      
         }      
     }      
     return false;      
 }  
複製代碼

下面我們看一下HashMap的ContainsKey方法和ContainsValue的源碼:

// HashMap是否包含key      
    public boolean containsKey(Object key) {      
        return getEntry(key) != null;      
    }  
複製代碼
// 返回“鍵爲key”的鍵值對      
    final Entry<K,V> getEntry(Object key) {      
        // 獲取哈希值      
        // HashMap將“key爲null”的元素存儲在table[0]位置,“key不爲null”的則調用hash()計算哈希值      
        int hash = (key == null) ? 0 : hash(key.hashCode());      
        // 在“該hash值對應的鏈表”上查找“鍵值等於key”的元素      
        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 != null && key.equals(k))))      
                return e;      
        }      
        return null;      
    }  
複製代碼
複製代碼
// 是否包含“值爲value”的元素      
    public boolean containsValue(Object value) {      
    // 若“value爲null”,則調用containsNullValue()查找      
    if (value == null)      
            return containsNullValue();      
     
    // 若“value不爲null”,則查找HashMap中是否有值爲value的節點。      
    Entry[] tab = table;      
        for (int i = 0; i < tab.length ; i++)      
            for (Entry e = tab[i] ; e != null ; e = e.next)      
                if (value.equals(e.value))      
                    return true;      
    return false;      
    }  
複製代碼

通過上面源碼的比較,我們可以得到如下不同地方:key和value是否允許null值。

其中key和value都是對象,並且不能包含重複key,但可以包含重複的value。通過上面的ContainsKey方法和ContainsValue的源碼我們可以很明顯的看出:

Hashtable中,key和value都不允許出現null值。但是如果在Hashtable中有類似put(null,null)的操作,編譯同樣可以通過,因爲key和value都是Object類型,但運行時會拋出NullPointerException異常,這是JDK的規範規定的。

HashMap中,null可以作爲鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值爲null。當get()方法返回null值時,可能是 HashMap中沒有該鍵,也可能使該鍵所對應的值爲null。因此,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵, 而應該用containsKey()方法來判斷。

HashMap和Hashtable的區別

HashMap是Hashtable的輕量級實現(非線程安全的實現),他們都完成了Map接口。主要的區別有:線程安全性,同步(synchronization),以及速度。

1.Hashtable繼承自Dictionary類,而HashMap是Java1.2引進的Map interface的一個實現。

2.HashMap允許將null作爲一個entry的key或者value,而Hashtable不允許。

3.HashMap是非synchronized,而Hashtable是synchronized,這意味着Hashtable是線程安全的,多個線程可以共享一個Hashtable;而如果沒有正確的同步的話,多個線程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。(在多個線程訪問Hashtable時,不需要自己爲它的方法實現同步,而HashMap 就必須爲之提供外同步(Collections.synchronizedMap))

4.另一個區別是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以當有其它線程改變了HashMap的結構(增加或者移除元素),將會拋出ConcurrentModificationException,但迭代器本身的remove()方法移除元素則不會拋出ConcurrentModificationException異常。但這並不是一個一定發生的行爲,要看JVM。這條同樣也是Enumeration和Iterator的區別。fail-fast機制如果不理解原理,可以查看這篇文章:http://www.cnblogs.com/alexlo/archive/2013/03/14/2959233.html

5.由於HashMap非線程安全,在只有一個線程訪問的情況下,效率要高於HashTable。

6.HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因爲contains方法容易讓人引起誤解。 

7.Hashtable中hash數組默認大小是11,增加的方式是 old*2+1。HashMap中hash數組的默認大小是16,而且一定是2的指數。

8..兩者通過hash值散列到hash表的算法不一樣:

,HashTbale是古老的除留餘數法,直接使用hashcode

int hash = key.hashCode();  
int index = (hash & 0x7FFFFFFF) % tab.length; 

而後者是強制容量爲2的冪,重新根據hashcode計算hash值,在使用hash 位與 (hash表長度 – 1),也等價取膜,但更加高效,取得的位置更加分散,偶數,奇數保證了都會分散到。前者就不能保證

複製代碼
int hash = hash(k);  
int i = indexFor(hash, table.length);  
  
static int hash(Object x) {  
  int h = x.hashCode();  
  
  h += ~(h << 9);  
  h ^= (h >>> 14);  
  h += (h << 4);  
  h ^= (h >>> 10);  
  return h;  
}  
  
static int indexFor(int h, int length) {  
  return h & (length-1);  
複製代碼

 

 

要注意的一些術語:

 1.sychronized意味着在一次僅有一個線程能夠更改Hashtable。就是說任何線程要更新Hashtable時要首先獲得同步鎖,其它線程要等到同步鎖被釋放之後才能再次獲得同步鎖更新Hashtable。

 2.Fail-safe和iterator迭代器相關。如果某個集合對象創建了Iterator或者ListIterator,然後其它的線程試圖“結構上”更改集合對象,將會拋出ConcurrentModificationException異常。但其它線程可以通過set()方法更改集合對象是允許的,因爲這並沒有從“結構上”更改集合。但是假如已經從結構上進行了更改,再調用set()方法,將會拋出IllegalArgumentException異常。

 3.結構上的更改指的是刪除或者插入一個元素,這樣會影響到map的結構。

 代碼演示部分如下:

先看個Hashtable正常輸出的示例:

Hashtable table = new Hashtable();  
table.put("a-key", "a-value");  
table.put("b-key", "b-value");  
table.put("c-key", "c-value");  

輸出如下:

a-key - a-value  
c-key - c-value  
b-key - b-value  

再看個Hashtable拒絕null的示例:

table.put(null, "a-value");  

運行之後異常如下:

Exception in thread "main" java.lang.NullPointerException  
at java.util.Hashtable.put(Hashtable.java:399)  
at com.darkmi.sandbox.HashtableTest.main(HashtableTest.java:20)  

HashMap示例:

HashMap map = new HashMap();  
map.put(null, "a-value");  
map.put("b-key", null);  
map.put("c-key", null);  
b-key - null  
null - a-value  
c-key - null  

PS:從上面的示例我們倒是可以發現Hashtable與HashMap相同的一點:無序存放。

3.兩者的遍歷方式大同小異,Hashtable僅僅比HashMap多一個elements方法。

Enumeration em = table.elements();  
while (em.hasMoreElements()) {  
String obj = (String) em.nextElement();  
System.out.println(obj);   
}  

HashMap和HashTable都能通過values()方法返回一個 Collection ,然後進行遍歷處理:

Collection coll = map.values();  
Iterator it = coll.iterator();  
while (it.hasNext()) {  
String obj = (String) it.next();  
System.out.println(obj);  
}  

兩者也都可以通過 entrySet() 方法返回一個 Set , 然後進行遍歷處理:

複製代碼
Set set = table.entrySet();  
Iterator it = set.iterator();  
while (it.hasNext()) {  
Entry entry = (Entry) it.next();  
System.out.println(entry.getKey() + " - " + entry.getValue());  
  
} 
複製代碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章