基本环境
Mac OS X EI Capitan 版本 10.11.6
AndroidStudio 2.3.2
Java API为Android API 25 Platform下的 Java API
源码基于 /Users/du/Library/Android/sdk/platforms/android-25/android.jar!/java/util/Hashtable.class
Hashtable的实现原理
● HashMap基于hashing的原理,进行存储 HashtableEntry[由每个键值对构造],使用拉链法解决哈希地址冲突。
● 使用put(key, value)方法来存储键值对,先计算出key的hashCode值,然后构造出HashtableEntry,根据hashCode值在哈希表中的索引决定每个HashtableEntry的存储位置。
● 使用get(key)来获取value,先计算出key的hashCode值,然后根据hashCode值在哈希表中的索引循环遍历链表查找value。
Hashtable的特点
● 基于数组实现,数组里的元素是一个单向链表[参考哈希表解决冲突方法之拉链法]
● key不可重复,value可重复,key、value不能为null
● 线程安全
Hashtable的成员变量
//哈希表
private transient HashtableEntry<K,V>[] table;
//哈希表中HashtableEntry的个数,即关键字的集合大小,非哈希表长度
private transient int count;
//当哈希表的大小超过超过阈值的时候要进行扩容,阈值 = capacity * loadFactor
private int threshold;
//装填因子
private float loadFactor;
//Hashtable的改变次数,用来实现“fail-fast”机制。
//在并发集合中,其进行迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,
//并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你
private transient int modCount = 0;
Hashtable的内部类
//HashtableEntry代表了“拉链”的节点,每一个HashtableEntry代表了一个键值对,
//哈希表的"key-value键值对"都是存储在HashtableEntry数组中的。
static class HashtableEntry<K,V> implements Map.Entry<K,V> {
int hash;
final K key;
V value;
HashtableEntry<K,V> next;//next使得每个哈希地址相同的同义词形成链表。
protected HashtableEntry(int hash, K key, V value, HashtableEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
protected Object clone() {
return new HashtableEntry<>(hash, key, value,
(next==null ? null : (HashtableEntry<K,V>) next.clone()));
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
if (value == null)
throw new NullPointerException();
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry)o;
return key.equals(e.getKey()) && value.equals(e.getValue());
}
public int hashCode() {
return (Objects.hashCode(key) ^ Objects.hashCode(value));
}
public String toString() {
return key.toString()+"="+value.toString();
}
}
Hashtable的构造方法
//提供初始容量和装填因子进行构造
//不同于HashMap,这里的initialCapacity是哈希表真实的表长,HashMap是2^initialCapacity
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 HashtableEntry[initialCapacity];
threshold = (initialCapacity <= MAX_ARRAY_SIZE + 1) ? initialCapacity : MAX_ARRAY_SIZE + 1;
}
//提供初始容量进行构造
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
//空构造方法,哈希表长为11,装填因子为0.75
public Hashtable() {
this(11, 0.75f);
}
//提供一个Map进行构造
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
Hashtable的主要操作方法
1. put方法
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {//可克隆,可序列化
//同步,保证线程安全
public synchronized V put(K key, V value) {
//value不能为空,会报NullPointerException
if (value == null) {
throw new NullPointerException();
}
HashtableEntry tab[] = table;
//根据key计算hash值
int hash = hash(key);
//根据hash值和哈希表长度[即数组容量],找到索引值
//hash & 0x7FFFFFFF 操作,保证为正数
int index = (hash & 0x7FFFFFFF) % tab.length;
//遍历table[i]位置的链表,若有相同的key,则用新值覆盖旧值
for (HashtableEntry<K,V> 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) {
//如果达到了扩容阈值,则进行扩容,容量是原来的 2 * oldCapacity + 1
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
//构造新的HashtableEntry结点,添加到表头
HashtableEntry<K,V> e = tab[index];
tab[index] = new HashtableEntry<>(hash, key, value, e);
count++;
return null;
}
//如果达到了扩容阈值,则进行扩容,容量是原来的 2 * oldCapacity + 1
protected void rehash() {
int oldCapacity = table.length;
HashtableEntry<K,V>[] oldMap = table;
//容量是原来的 2 * oldCapacity + 1
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
//新建一个长度为newCapacity的哈希表
HashtableEntry<K,V>[] newMap = new HashtableEntry[newCapacity];
modCount++;
//重新计算阀值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
//将oldMap中的元素拷贝到newMap中
for (int i = oldCapacity ; i-- > 0 ;) {
for (HashtableEntry<K,V> old = oldMap[i] ; old != null ; ) {
HashtableEntry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
}
2. get方法
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {//可克隆,可序列化
//synchronized同步方法,保证线程安全
public synchronized V get(Object key) {
HashtableEntry tab[] = table;
//根据key计算hash值
int hash = hash(key);
//根据hash值和哈希表长度[即数组容量],找到索引值
int index = (hash & 0x7FFFFFFF) % tab.length;
//根据索引查找它所在的链表,并在单向链表中查找该元素
for (HashtableEntry<K,V> e = tab[index] ; e != null ; e = e.next) {
//hash值相等,且key相等时返回HashtableEntry的value值
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
}
3. remove方法
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {//可克隆,可序列化
//synchronized同步方法,保证线程安全
public synchronized V remove(Object key) {
HashtableEntry tab[] = table;
//根据key计算hash值
int hash = hash(key);
//根据hash值和哈希表长度[即数组容量],找到索引值
int index = (hash & 0x7FFFFFFF) % tab.length;
//根据索引查找它所在的链表,并在单向链表中查找该元素
for (HashtableEntry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
//hash值相等,且key相等时删除该元素[实质上将该元素的后继元素e.next 链接到 该元素的前继元素prev.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的注意点
● key 和 value 均不可为 null 。
● Hashtable的方法是同步的,所以线程安全。
● Hashtable的默认初始大小是11,容量尽量使用素数、奇数,因为简单的取模,哈希的结果会更加均匀。
● 在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替代Hashtable;如果需要实现高并发下的线程安全,推荐使用ConcurrentHashMap代替Hashtable。
以上的意思呢,就是Hashtable已经不推荐使用了。
● 每一版本的JDK,都会对HashMap和Hashtable的内部实现做优化。因为有老的代码还在使用它,所以优化之后,这些老的代码也能获得性能提升。
参考链接