HashTable同样是基于哈希表实现的,其中的每个元素是一个key-value
对,内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。它在很大程度上和HashMap的实现差不多,不过相对的功能较少,目前已经很少使用了,下面来看看HashTable的具体内容。
HashTable的继承关系:
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
}
从中可以看出HashTable继承Dictionary类,实现Map接口。其中Dictionary类是任何可将键映射到相应值的类(如 Hashtable)的抽象父类。每个键和每个值都是一个对象。在任何一个 Dictionary 对象中,每个键至多与一个值相关联。
Map
接口,是"key-value键值对"接口。即接口中的每个元素都是一对, 以key-value的形式保存,并且Key是不重复的,元素的存储位置由key决定。也就是可以通过key去寻找key-value的位置,从而得到value的值。适合做查找工作。Cloneable
接口,能被克隆。Cloneable是标记型的接口,它们内部都没有方法和属性,实现 Cloneable来表示该对象能被克隆,能使用Object.clone()方法。如果没有实现 Cloneable的类对象调用clone()就会抛出CloneNotSupportedException。Serializable
接口,它支持序列化。public interface Serializable类通过实现 java.io.Serializable 接口以启用其序列化功能。/**
* The hash table data.
*/
private transient Entry<?,?>[] table;
/**
* Hashtable bucket collision list entry
*/
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
HashTable提供了两种遍历方式:
private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
}
其中:
Enumeration
:利用枚举的方式从前向后遍历集合,不支持快速失败机制。Enumeration迭代器只能遍历Vector、Hashtable这种古老的集合,因此通常不要使用它,除非在某些极端情况下,不得不使用Enumeration,否则都应该选择Iterator迭代器。Iterator
:迭代器遍历方式。支持快速失败机制,目前使用较多的遍历方式。{
private transient Entry<?,?>[] table;
private transient int count;
private int threshold;
private float loadFactor;
private transient int modCount = 0;
}
table
:为一个Entry[]数组类型,Entry代表了“拉链”的节点,每一个Entry代表了一个键值对,哈希表的"key-value键值对"都是存储在Entry数组中的。count
:HashTable的大小,注意这个大小并不是HashTable的容器大小,而是他所包含Entry键值对的数量。threshold
:Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值=“容量*加载因子”。loadFacto
r:加载因子。modCount
:用来实现“fail-fast”机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你。/**
* Constructs a new, empty hashtable with a default initial capacity (11)
* and load factor (0.75).
*/
public Hashtable() {
this(11, 0.75f);
}
/**
* Constructs a new, empty hashtable with the specified initial capacity
* and default load factor (0.75).
*
* @param initialCapacity the initial capacity of the hashtable.
* @exception IllegalArgumentException if the initial capacity is less
* than zero.
*/
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* Constructs a new, empty hashtable with the specified initial
* capacity and the specified load factor.
*
* @param initialCapacity the initial capacity of the hashtable.
* @param loadFactor the load factor of the hashtable.
* @exception IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive.
*/
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 = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
/**
* Constructs a new hashtable with the same mappings as the given
* Map. The hashtable is created with an initial capacity sufficient to
* hold the mappings in the given Map and a default load factor (0.75).
*
* @param t the map whose mappings are to be placed in this map.
* @throws NullPointerException if the specified map is null.
* @since 1.2
*/
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
put
方法:将指定 key 映射到此哈希表中的指定 value。注意这里键key和值value都不可为空。
put
方法的整个处理流程是:计算key的hash值,根据hash值获得key在table数组中的索引位置,然后迭代该key处的Entry链表(我们暂且理解为链表),若该链表中存在一个这个的key对象,那么就直接替换其value值即可,否则在将改key-value节点插入该index索引位置处。/**
* Maps the specified key
to the specified
* value
in this hashtable. Neither the key nor the
* value can be null
.
*
* The value can be retrieved by calling the get
method
* with a key that is equal to the original key.
*
* @param key the hashtable key
* @param value the value
* @return the previous value of the specified key in this hashtable,
* or null
if it did not have one
* @exception NullPointerException if the key or value is
* null
* @see Object#equals(Object)
* @see #get(Object)
*/
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;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
get
方法:
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key.equals(k))},
* then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped, or
* {@code null} if this map contains no mapping for the key
* @throws NullPointerException if the specified key is null
* @see #put(Object, Object)
*/
@SuppressWarnings("unchecked")
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
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 (V)e.value;
}
}
return null;
}
remove
方法:/**
* Removes the key (and its corresponding value) from this
* hashtable. This method does nothing if the key is not in the hashtable.
*
* @param key the key that needs to be removed
* @return the value to which the key had been mapped in this hashtable,
* or null
if the key did not have a mapping
* @throws NullPointerException if the key is null
*/
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
HashTable和HashMap存在很多的相同点,但是他们还是有几个比较重要的不同点。
//当HashMap遇到为null的key时,它会调用putForNullKey方法来进行处理。
//对于value没有进行任何处理,只要是对象都可以。
if (key == null) return putForNullKey(value);
//而当HashTable遇到null时,他会直接抛出NullPointerException异常信息。
if (value == null) throw new NullPointerException();
对比项 | HashTable | HashMap |
---|---|---|
继承的父类和接口 | 继承Dictionary类,实现了Map, Cloneable, Serializable接口 | 继承AbstractMap类,实现了Map, Cloneable, Serializable接口 |
默认容量 | 11 | 16 |
Table的初始化时机 | 直接在构造对象时初始化 | 第一次添加数据时初始化 |
线程是否安全 | 由于加了synchronized ,所以线程安全。(但并不是绝对安全,由于HashTable效率低下且已被淘汰,一般在需要线程安全的场景时选择ConcurrentHashMap ) |
线程不安全 |
遍历方式 | 既可以使用Iterator ,也可以使用Enumeration |
只可以使用Iterator |
是否支持fast-fail(快速失败机制) | 使用Iterator 遍历时支持,使用Enumeration 遍历时不支持 |
使用Iterator 遍历时支持 |
是否可以添加重复的key | 不能,会使用新值替换旧值 | 不能,会使用新值替换旧值 |
是否接受值为null的Key或Value | Key或Value都不可以为空,会抛出NullPointerException 异常 |
Key或Value都可以为空 |
根据hash值计算数组下标的算法 | 没有扰动处理,哈希冲突重复率高,使用(hash & 0x7FFFFFFF) % tab.length; 确保index 为正数并且不会越界 |
有扰动处理,哈希冲突重复率低,使用h & (length-1); 确保index 不会越界 |
Entry数组的长度 | 11 | 16(并且始终为2的幂次方) |
LoadFactor负荷因子 | 0.75 | 0.75 |
负荷超过阈值时,内部数据的调整方式 | 将容量变为原来的2倍加1 | 将容量变为原来的2倍 |