【1】HashMap和HashTable异同
HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。
主要的区别有:
- 线程安全性–同步(synchronization),
- 键值是否允许为null,
- 类继承,
- 初始化及扩容
- 迭代器以及速度。
① 线程安全
HashMap是非synchronized,而Hashtable是synchronized。Hashtable 所有的元素操作都是 synchronized 修饰的,而 HashMap 并没有。
这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable。而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
HashTable只能由一个线程操作, ConcurrentHashMap可以让一个线程操作第一个Segment,另一个线程操作另一个Segment
② NULL
Hashtable 是不允许键或值为 null 的,HashMap 的键值则都可以为 null(只有一个键允许为null)。
HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键(key)和值(value),而Hashtable则不行)。
为什么 Hashtable 是不允许 KEY 和 VALUE 为 null, 而 HashMap 则可以?
Hashtable中如果key/ value 为 null 会直接抛出空指针异常,而 HashMap 的逻辑对 null 作了特殊处理。
③ 类继承
Hashtable如下:
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {
HashMap如下:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
可以看出两者继承的类不一样,Hashtable 继承了 Dictionary类,而 HashMap 继承的是 AbstractMap 类。
Dictionary 是 JDK 1.0 添加的,很古老的一个字典类。其javadoc如下所示:
Dictionary是键值对映射类的抽象父类,例如Hashtable。 每一个键和值都是一个object。每个键最多关联一个值。 给定一个Dictionary对象和一个键,关联的元素可以被找到。 任何非 null 对象都可以用作键和值。 通常,这个类的实现应该使用equals方法来确定两个键是否相同 注意:这个类已经过时。新的实现应该实现Map接口,而不是继承这个类
④ 初始化和容量扩容
HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75。
当现有容量大于总容量 * 负载因子
时,HashMap 扩容规则为当前容量翻倍,Hashtable 扩容规则为当前容量翻倍 + 1。
⑤ 迭代器
另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的Enumerator迭代器不是fail-fast的。
所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。
但这并不是一个一定发生的行为,要看JVM的实现。这同样也是Enumeration和Iterator的区别。
参考博文:浅谈从fail-fast机制到CopyOnWriteArrayList使用
⑥ 速度与性能
由于Hashtable是线程安全的也是synchronized,所以通常它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
如果要线程安全又要保证性能,建议使用 JUC 包下的 ConcurrentHashMap。
【2】相关重要术语
① sychronized意味着在一次仅有一个线程能够更改Hashtable。
就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。HashTable将底层整个hash表都锁了起来,故而在多线程的环境下,使用HashTable虽然安全但是性能十分低下。
另外需要注意的是HashTable并非绝对安全,比如在复合操作的情况下,如下所示:
if(!table.contains(XX)){ //如果在这中间,被其他线程拿到了锁呢? table.put(XX); }
② fail-fast机制
"快速失败"也就是fail-fast,它是Java集合的一种错误检测机制。
当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。
例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
但其它线程可以通过set()方法更改集合对象是允许的,因为这并没有从“结构上”更改集合。但是假如已经从结构上进行了更改,再调用set()方法,将会抛出IllegalArgumentException异常。
结构上的更改指的是类似于删除或者插入一个元素,这样会影响到map的结构。
③ 我们能否让HashMap同步?
HashMap可以通过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap); // 或者直接使用ConcurrentHashMap(JDK1.5);