java—HashMap與Hashtable的源碼比較

java—HashMap與Hashtable的源碼比較

本文主要記錄通過源碼閱讀的方式比較HashMap和HashTable


1. HashMap、HashTable的類結構

HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

HashMap繼承於AbstractMap,Hashtable繼承於Dictionary。

public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable

通過查閱jdk,有下面這句話:

NOTE: This class is obsolete. New implementations should implement the Map interface, rather than extending this class.

得知Dictionary類已經過時了,而推薦實現Map接口。

而Hashtable也是一個過時的集合類,從jdk1.0開始就存在了。在Java 4中被重寫了,實現了Map接口,所以自此以後也成了java集合框架的一部分。

2. 主要區別

1. 線程安全性

HashMap是線程不安全的,Hashtable是線程安全的。Hashtable的線程安全是用synchronized關鍵字實現的。

public synchronized int size();
public synchronized boolean isEmpty();
public synchronized V get(Object key);
public synchronized V put(K key, V value);

以上方法是Hashtable源碼裏的,其實和HashMap幾乎一樣,只是多了synchronized關鍵字。則Hashtable是線程安全的,多個線程可以共享一個Hashtable。而如果沒有正確同步的話,多個線程不能共享HashMap。Java 5 提供了ConcurrentHashMap,它是Hashtable的替代,比Hashtable的擴展更好

2. null的鍵和值

HashMap是可以接受null的鍵和值的,而Hashtable則不允許。

先從Hashtable的put()方法講起:

public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}

// Makes sure the key is not already in the hashtable.
Entry<!--?,? > tab[] = table;<br ?--> int hash = key.hashCode();
int index = (hash &amp; 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) &amp;&amp; entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}

addEntry(hash, key, value, index);
return null;
}

在put()方法裏,首先會對value進行檢查,若爲null,則拋出NullPointerException。對於key,則直接使用key.hashcode(),若key爲null,則仍會拋出NullPointerException。

下面再看下HashMap裏的put()實現:

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) &amp; hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &amp;&amp;
((k = p.key) == key || (key != null &amp;&amp; key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &amp;&amp;
((k = e.key) == key || (key != null &amp;&amp; key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

put()會調用putVal(),而putVal()中則不會對value做null的檢查,再看看key,是如果獲得null值的key的hash值。這是用到了HashMap裏的hash()。

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

很明顯,若key爲null,則hash值用0。這便是HashMap如何支持null值的key和value。

3. 速度

Hashtable是線程安全的,所以在單線程環境下它比HashMap要慢。如果不需要同步,只需要單一線程,那麼使用HashMap性能要好過Hashtable。

3. 讓HashMap同步

HashMap可以通過下面的語句進行同步:

Map m = Collections.synchronizeMap(hashMap);

實現方法仍然是給方法加上synchronized關鍵字。

public int size() {
synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
}

public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}
}
public void putAll(Map<? extends K, ? extends V> map) {
synchronized (mutex) {m.putAll(map);}
}
public void clear() {
synchronized (mutex) {m.clear();}
}

private transient Set keySet;
private transient Set<Map.Entry<K,V>> entrySet;
private transient Collection values;

public Set keySet() {
synchronized (mutex) {
if (keySet==null)
keySet = new SynchronizedSet<>(m.keySet(), mutex);
return keySet;
}
}

4. 疑惑

看別人的文章裏說,HashMap和HashTable的迭代器是不同的,HashMap用的是iterator是fail-fast的,而HashTable用的是enumerator不是fail-fast的。但我看1.8jdk裏的Hashtable的enumerator 如下:

private class Enumerator implements Enumeration, Iterator

是有實現iterator接口的,也就是Hashtable其實是iterator和Enumeration都有支持的。
後續再補充吧。對於fail-fast還是沒透徹理解。

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