(一)Hashtable和HashMap简介
Hashtable和HashMap的底层都是数组+链表实现:
Hashtable:
(1)Hashtable 是一个散列表,它存储的内容是键值对(key-value)映射。
(2)Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。
(3)HashTable直接使用对象的hashCode。
源码如下:
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
HashMap
(1)由数组+链表组成的,基于哈希表的Map实现,数组是HashMap的主体,
链表则是主要为了解决哈希冲突而存在的。
(2)不是线程安全的,HashMap可以接受为null的键(key)和值(value)。
(3)HashMap重新计算hash值
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap是Hashtable的轻量级实现(非线程安全的实现)都完成了Map接口,主要区别在于能否键对值<K,V>能为null。
同时其内部方法有区别:HashMap中将Hashtable的contains方法去掉了,改为containsvalue和containsKey,避免混淆。Hashtable继承于Dictionary类,而HashMap是java 1.2 引进的Map接口一个实现。HashMap就效率而言高于Hashtable。
关于Map:
Map是一种把键对象和值对象进行关联的容器,而一个值对象又可以是一个Map,依次类推,这样就可形成一个多级映射。对于键对象来说,像Set一样,一个Map容器中的键对象不允许重复,这是为了保持查找结果的一致性;如果有两个键对象一样,那你想得到那个键对象所对应的值对象时就有问题了,可能你得到的并不是你想的那个值对象,结果会造成混乱,所以键的唯一性很重要,也是符合集合的性质的。当然在使用过程中,某个键所对应的值对象可能会发生变化,这时会按照最后一次修改的值对象与键对应。对于值对象则没有唯一性的要求。你可以将任意多个键都映射到一个值对象上,这不会发生任何问题(不过对你的使用却可能会造成不便,你不知道你得到的到底是那一个键所对应的值对象)。Map有两种比较常用的实现: HashMap和TreeMap。HashMap也用到了哈希码的算法,以便快速查找一个键,TreeMap则是对键按序存放,因此它便有一些扩展的方法,比如firstKey(),lastKey()等,你还可以从TreeMap中指定一个范围以取得其子Map。键和值的关联很简单,用pub (Object key,Object value)方法即可将一个键与一个值对象相关联。用get(Object key)可得到与此key对象所对应的值对象。
(二)Hashtable和HashMap的区别:
1同步性
- Hashtable的几乎所有函数都是同步的,都有Synchronized关键字;即它是线程安全的,支持多线程。
下图为hashtable中的部分函数:
public synchronized Enumeration<K> keys() {
return this.<K>getEnumeration(KEYS);
}
/**
* Returns an enumeration of the values in this hashtable.
* Use the Enumeration methods on the returned object to fetch the elements
* sequentially.
*
* @return an enumeration of the values in this hashtable.
* @see java.util.Enumeration
* @see #keys()
* @see #values()
* @see Map
*/
public synchronized Enumeration<V> elements() {
return this.<V>getEnumeration(VALUES);
}
/**
* Tests if some key maps into the specified value in this hashtable.
* This operation is more expensive than the {@link #containsKey
* containsKey} method.
*
* <p>Note that this method is identical in functionality to
* {@link #containsValue containsValue}, (which is part of the
* {@link Map} interface in the collections framework).
*
* @param value a value to search for
* @return <code>true</code> if and only if some key maps to the
* <code>value</code> argument in this hashtable as
* determined by the <tt>equals</tt> method;
* <code>false</code> otherwise.
* @exception NullPointerException if the value is <code>null</code>
*/
public synchronized boolean contains(Object value) {}
- HashMap的函数则是非同步的,它不是线程安全的。
下图为HashMap的部分源码:
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
-
如何实现HashMap的同步
- 一:可以通过静态方法:Map m =Collections.synchronizedMap(new HashMap())来达到同步的效果
- 二:接使用JDK 5.0之后提供的java.util.concurrent包里的ConcurrentHashMap类。
-
由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢,效率可能会低,如果不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
HashMap不能保证随着时间的推移Map中的元素次序是不变的。
- sychronized意味着在一次仅有一个线程能够更改Hashtable。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。
2.继承的父类不同:
- HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
- Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
3.对null值的处理不同
- Hashtable的key、value都不可以为null,会空指针异常,即 throw new NullPointerException();
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
- HashMap的key、value都可以为null。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
4 支持的遍历种类不同
- Hashtable支持Iterator(迭代器)和Enumeration(枚举器)两种方式遍历
- HashMap只支持Iterator(迭代器)遍历。
HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
- Fail-safe和iterator迭代器相关。如果某个集合对象创建了Iterator或者ListIterator,然后其它的线程试图“结构上”更改(结构上的更改指的是删除或者插入一个元素,这样会影响到map的结构)集合对象,将会抛出ConcurrentModificationException异常。但其它线程可以通过set()方法更改集合对象是允许的,因为这并没有从“结构上”更改集合。但是假如已经从结构上进行了更改,再调用set()方法,将会抛出IllegalArgumentException异常。
通过Iterator迭代器遍历时,遍历的顺序不同
- HashMap是“从前向后”的遍历数组;再对数组具体某一项对应的链表,从表头开始进行遍历。
- Hashtabl是“从后往前”的遍历数组;再对数组具体某一项对应的链表,从表头开始进行遍历。
6 容量的初始值 和 增加方式都不一样
- Hashtable默认的容量大小是11;增加容量时,每次将容量变为“原始容量x2 + 1”。
- HashMap默认的容量大小是16;增加容量时,每次将容量变为“原始容量x2”。
7 添加key-value时的hash值算法不同
- HashMap添加元素时,是使用自定义的哈希算法。
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
- Hashtable没有自定义哈希算法,而直接采用的key的hashCode()。
8 部分API不同
- Hashtable支持contains(Object value)方法,而且重写了toString()方法;
- HashMap不支持contains(Object value)方法,没有重写toString()方法。