父类 | 安全 | 是否可以null键null值 | ||
---|---|---|---|---|
HashMap | AbstractMap | 不安全 | 允许null值和null键 | |
HashTable | Dictionary | 安全 | 不允许null这null键 |
看两个类的源码就可以知道:
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable{}
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {}
首先,要明白什么叫线程安全和线程不安全
线程安全:就是多线程访问时,采用了加锁机制,当一个线程访问某个数据时,进行加锁保护,其他线程不能进行访问,直到该线程任务结束,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全:就是不提供数据访问保护,有可能出现多个线程先后更改数据,造成所得到的数据是脏数据。
而synchronized关键字,可以给方法或者代码块加锁,实现同步,此时,遇到多线程访问时,数据就是安全的。看HashTable的源码可知,HashTable的方法,都是synchronized同步的,所以,HashTable是同步的,安全的。而HashMap中,所有的方法,均没有加锁,所以,HashMap是不安全的。部分源码如下:
HashTable部分源码:
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
public synchronized void putAll(Map<? extends K, ? extends V> t) {
for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
put(e.getKey(), e.getValue());
}
/**
* Clears this hashtable so that it contains no keys.
*/
public synchronized void clear() {
Entry tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )
tab[index] = null;
count = 0;
}
HashMap部分源码:
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> 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;
}
看源码,HashTable extends Dictionary<K,V>,这个Dictionary类,源码中:abstract public V put(K key, V value)上面的注解如下:
/**
* Maps the specified <code>key</code> to the specified
* <code>value</code> in this dictionary. Neither the key nor the
* value can be <code>null</code>.
* <p>
* If this dictionary already contains an entry for the specified
* <tt>key</tt>, the value already in this dictionary for that
* <tt>key</tt> is returned, after modifying the entry to contain the
* new element. <p>If this dictionary does not already have an entry
* for the specified <tt>key</tt>, an entry is created for the
* specified <tt>key</tt> and <tt>value</tt>, and <tt>null</tt> is
* returned.
* <p>
* The <code>value</code> can be retrieved by calling the
* <code>get</code> method with a <code>key</code> that is equal to
* the original <code>key</code>.
*
* @param key the hashtable key.
* @param value the value.
* @return the previous value to which the <code>key</code> was mapped
* in this dictionary, or <code>null</code> if the key did not
* have a previous mapping.
* @exception NullPointerException if the <code>key</code> or
* <code>value</code> is <code>null</code>.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.util.Dictionary#get(java.lang.Object)
*/
abstract public V put(K key, V value);
而且,还说NullPointerException if the <code>key</code> or <code>value</code> is <code>null</code>,当key或者value为null时,会抛出NullPointerException 。
父类Dictionary的put()方法在这里说了:Neither the key nor the value can be <code>null</code>.key或者value,都不允许为空,那子类的put()方法,继承自父类,当然不允许null值和null键了;
另外,如果仔细看HashTable的源码,他自己也有这么一句:
* This class implements a hash table, which maps keys to values. Any
* non-<code>null</code> object can be used as a key or as a value. <p>
另外,这个Dictory顶部注解还说:
* The <code>Dictionary</code> class is the abstract parent of any
* class, such as <code>Hashtable</code>, which maps keys to values.
* Every key and every value is an object. In any one <tt>Dictionary</tt>
* object, every key is associated with at most one value. Given a
* <tt>Dictionary</tt> and a key, the associated element can be looked up.
* Any non-<code>null</code> object can be used as a key and as a value.
* <p>
* As a rule, the <code>equals</code> method should be used by
* implementations of this class to decide if two keys are the same.
* <p>
* <strong>NOTE: This class is obsolete. New implementations should
* implement the Map interface, rather than extending this class.</strong>
*
这是HashTable这样键值对类的父类,.........这个类过时了,新的实现者,应该去实现Map接口,而不是这个。
----------------------------------------
而HashMap的源码中:
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> 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;
}
源码中未对key和value做任何限制,而且方法中,key是null,那就放putForNullKey好了,根本不管这个。
所以,HashTable不允许null key or null value,而HashMap可以。
笔者多年前,面试中,被问到这个问题,说完两个集合的安全问题后,面试官问我,HashTable的同步锁是加在哪里?有什么区别?显然,如果没有看过源码,是不能确定这个synchronized是加在哪里的。
HashTable的synchronized,锁是加在方法上的。加在不同的地方,区别是锁对象不同:
同步代码块:任意对象
同步方法:this
静态同步方法:类名.class