Hashtable类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值。为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。
HashMap是Hashtable的轻量级实现。
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是 Java 1.2引进的Map接口的一个实现。
Hashtable也是继承了Map接口。它们的不同是 Hashtable(since JDK1.0)就继承了Dictionary这个抽象类,而HashMap(since JDK1.2)继承的则是AbstractMap这个抽象类。因为在Hashtable中看到继承Map后所实现的方法是JDK1.2版本时加上去的,估计是在JDK 1.2开发时出于统一的考虑使得Hashtable也继承了Map接口。
为一个Entry[]数组类型,Entry代表了“拉链”的节点,每一个Entry代表了一个键值对,哈希表的”key-value键值对”都是存储在Entry数组中的。
HashTable的大小,这个大小并不是HashTable的容器大小,而是他所包含Entry键值对的数量。
Hashtable的阈值,用于判断是否需要调整Hashtable的容量。
threshold的值=容量count * 加载因子loadFactor。
加载因子。
用来实现“fail-fast”机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你(你已经出错了)。
初始大小为11(Hashmap桶的初始大小为16)
public Hashtable() {
this(11, 0.75f);
}
自定义初始化的容量大小和默认的加载因子 (0.75) 构造一个新的空哈希表。
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
public Hashtable(int initialCapacity, float loadFactor) {
//初始容量是否合法
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//加载因子是否合法
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
//初始化table,获得大小为initialCapacity的table数组
table = new Entry[initialCapacity];
//计算阀值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//初始化HashSeed值
initHashSeedAsNeeded(initialCapacity);
}
initHashSeedAsNeeded方法用于初始化hashSeed参数,其中hashSeed用于计算key的hash值,它与key的hashCode进行按位异或运算。这个hashSeed是一个与实例相关的随机值,主要用于解决hash冲突。
private int hash(Object k) {
return hashSeed ^ k.hashCode();
}
构造一个与给定的 Map 具有相同映射关系的新哈希表。
public Hashtable(Map extends K, ? extends V> t) {
//设置table容器大小,其值==t.size * 2 + 1
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
put方法的整个处理流程是:计算key的hash值,根据hash值获得key在table数组中的索引位置,然后迭代该key处的Entry链表(我们暂且理解为链表),若该链表中存在一个这个的key值,那么就直接替换其value值即可,否则在将该key-value节点插入该index索引位置处。
//同步
public synchronized V put(K key, V value) {
// 不允许value为null
if (value == null) {
throw new NullPointerException();
}
/*
* 确保key在table[]是不重复的
* 处理过程:
* 1、计算key的hash值,确认在table[]中的索引位置
* 2、迭代index索引位置,如果该位置处的链表中存在一个一样的key,则替换其value,返回旧值
*/
Entry tab[] = table;
int hash = hash(key); //计算key的hash值
//寻找该key的索引位置
//hash & 0x7FFFFFFF为确保结果hash值为正,然后对table.length取余找到位置
int index = (hash & 0x7FFFFFFF) % tab.length;
//迭代,寻找该key,替换
for (Entry e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) { //如果容器中的元素数量已经达到阀值,则进行扩容操作
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// 在索引位置处插入一个新的节点
Entry e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
//容器中元素+1
count++;
return null;
}
HashTable的扩容操作,在put方法中,如果需要向table[]中添加Entry元素,会首先进行容量校验,如果容量已经达到了阀值,HashTable就会进行扩容处理rehash(),扩大为新容量=旧容量 * 2 + 1
protected void rehash() {
int oldCapacity = table.length;
//元素
Entry[] oldMap = table;
//新容量=旧容量 * 2 + 1
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
return;
newCapacity = MAX_ARRAY_SIZE;
}
//新建一个size = newCapacity 的HashTable
Entry[] newMap = new Entry[];
modCount++;
//重新计算阀值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//重新计算hashSeed
boolean rehash = initHashSeedAsNeeded(newCapacity);
table = newMap;
//将原来的元素拷贝到新的HashTable中
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry old = oldMap[i] ; old != null ; ) {
Entry e = old;
old = old.next;
if (rehash) {
e.hash = hash(e.key);
}
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
计算hash值,由hash值得出key对应的索引
private int hash(Object k) {
return hashSeed ^ k.hashCode();
}
计算key的hash值,判断在table数组中的索引位置,然后迭代链表,匹配直到找到相对应key的value,若没有找到返回null。
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
前置条件:
Hashtable<String, Object> hashtable = null;
@Before
public void init(){
hashtable = new Hashtable<String, Object>();
hashtable.put("now", "you");
hashtable.put("see", "me");
hashtable.put("cause", "i");
hashtable.put("let", "be");
}
@Test
public void HashTableTraversalTest1(){
Iterator
结果打印:
see:me
now:you
let:be
cause:i
@Test
public void HashTableTraversalTest2(){
Iterator iterator = hashtable.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
System.out.println(key+":"+hashtable.get(key));
}
}
@Test
public void HashTableTraversalTest3(){
Enumeration enumeration = hashtable.keys();
while (enumeration.hasMoreElements()) {
String string = (String) enumeration.nextElement();
System.out.println(string+":"+hashtable.get(string));
}
}
@Test
public void HashTableTraversalValuesTest4(){
Collection
两个类的继承体系有些不同。虽然都实现了Map、Cloneable、Serializable三个接口。但是HashMap继承自抽象类AbstractMap,而HashTable继承自抽象类Dictionary。其中Dictionary类是一个已经被废弃的类。
Hashtable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
HashTable比HashMap多了两个公开方法。一个是elements,这来自于抽象类Dictionary,鉴于该类已经废弃,所以这个方法也就没什么用处了。另一个多出来的方法是contains,这个多出来的方法也没什么用,因为它跟containsValue方法功能是一样的。
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
Entry tab[] = table;
for (int i = tab.length ; i-- > 0 ;) {
for (Entry e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}
public boolean containsValue(Object value) {
return contains(value);
}
Hashtable的方法是同步的,几乎每个方法都做了同步。而HashMap则是非同步的,所以在多线程场合要手动同步HashMap,这个区别就像Vector和ArrayList一样。
java.util.HashTable
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
public Set keySet() {
if (keySet == null)
//使用了Collections.synchronizedSet进行了同步包装。
keySet = Collections.synchronizedSet(new KeySet(), this);
return keySet;
}
HashMap是支持null键和null值的,而HashTable在遇到null时,会抛出NullPointerException异常。即Hashtable不允许null值(key和value都不可以),HashMap允许null值(key和value都可以)。
两个遍历方式的内部实现上不同。
Hashtable使用Enumeration,HashMap使用Iterator。
Hashtable比HashMap多一个elements方法用于遍历。
哈希值的使用不同,Hashtable直接使用对象的hashCode,而HashMap重新计算hash值,而且用于代替求模。
HashTable已经被淘汰了。新写代码不推荐使用HashTable。
//HashTable的类注释:
If a thread-safe implementation is not needed, it is recommended to use HashMap in place of Hashtable. If a thread-safe highly-concurrent implementation is desired, then it is recommended to use java.util.concurrent.ConcurrentHashMap in place of Hashtable.
翻译过来即不需要线程安全,那么使用HashMap,如果需要线程安全,那么使用ConcurrentHashMap。
http://www.cnblogs.com/xinzhao/p/5644175.html
http://cmsblogs.com/?p=618
http://blog.csdn.net/zheng0518/article/details/42199477