java—HashMap與Hashtable的源碼比較
一井氢、前言
一直都知道HashMap是程谖瘢考的虹脯,所以今天把HashMap的源碼看了一遍驴娃,然后又想起了HashTable,便想做一個(gè)比較循集。
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類已經(jīng)過時(shí)了疆柔,而推薦實(shí)現(xiàn)Map接口。
而Hashtable也是一個(gè)過時(shí)的集合類镶柱,從jdk1.0開始就存在了旷档。在Java 4中被重寫了,實(shí)現(xiàn)了Map接口歇拆,所以自此以后也成了java集合框架的一部分鞋屈。
二、主要區(qū)別
1. 線程安全性
HashMap是線程不安全的故觅,Hashtable是線程安全的谐区。Hashtable的線程安全是用synchronized關(guān)鍵字實(shí)現(xiàn)的。
public synchronized int size();
public synchronized boolean isEmpty();
public synchronized V get(Object key);
public synchronized V put(K key, V value);
以上方法是Hashtable源碼里的逻卖,其實(shí)和HashMap幾乎一樣,只是多了synchronized關(guān)鍵字昭抒。則Hashtable是線程安全的评也,多個(gè)線程可以共享一個(gè)Hashtable。而如果沒有正確同步的話灭返,多個(gè)線程不能共享HashMap盗迟。Java 5 提供了ConcurrentHashMap,它是Hashtable的替代熙含,比Hashtable的擴(kuò)展更好
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;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
在put()方法里怎静,首先會(huì)對(duì)value進(jìn)行檢查邮弹,若為null,則拋出NullPointerException蚓聘。對(duì)于key腌乡,則直接使用key.hashcode(),若key為null夜牡,則仍會(huì)拋出NullPointerException与纽。
下面再看下HashMap里的put()實(shí)現(xiàn):
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) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && 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 &&
((k = e.key) == key || (key != null && 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()會(huì)調(diào)用putVal(),而putVal()中則不會(huì)對(duì)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是線程安全的胀溺,所以在單線程環(huán)境下它比HashMap要慢。如果不需要同步皆看,只需要單一線程仓坞,那么使用HashMap性能要好過Hashtable。
三腰吟、讓HashMap同步
HashMap可以通過下面的語句進(jìn)行同步:
Map m = Collections.synchronizeMap(hashMap);
實(shí)現(xiàn)方法仍然是給方法加上synchronized關(guān)鍵字无埃。
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<K> keySet;
private transient Set<Map.Entry<K,V>> entrySet;
private transient Collection<V> values;
public Set<K> keySet() {
synchronized (mutex) {
if (keySet==null)
keySet = new SynchronizedSet<>(m.keySet(), mutex);
return keySet;
}
}
四、疑惑
看別人的文章里說毛雇,HashMap和HashTable的迭代器是不同的嫉称,HashMap用的是iterator是fail-fast的,而HashTable用的是enumerator不是fail-fast的灵疮。但我看1.8jdk里的Hashtable的enumerator 如下:
private class Enumerator<T> implements Enumeration<T>, Iterator<T>
是有實(shí)現(xiàn)iterator接口的织阅,也就是Hashtable其實(shí)是iterator和Enumeration都有支持的。
后續(xù)再補(bǔ)充吧震捣。對(duì)于fail-fast還是沒透徹理解荔棉。