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还是没透彻理解。

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