Hashtable的简要介绍
上一节学了HashMap,这次来了解一下这个过时的容器:Hashtable。Hashtable跟HashMap一样也是基于哈希表实现的,表内元素都是Key-Value键值对映射,K、V都不允许null值,发生Hash冲突时通过单链表解决冲突,在容量不足时会扩容并重新hash,扩容是原大小(默认初始大小为11)乘2再加1;Hashtable通过synchronized对整个表枷锁来实现线程安全。
Hashtable继承自Dictionary,实现了Map、Cloneable、Serializable接口。、
从源码分析HashTable
成员变量
private transient Entry<?,?>[] table;//这个就是用于存放元素的哈希表了
private transient int count;//表中包含的元素数量
private int threshold;//扩容阈值,由当前容量*负载因子决定
private float loadFactor;//默认是0.75
private static class Entry<K,V> implements Map.Entry<K,V> {//Entry内存有hash、key、value、next
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;
}
/*...以下省略...*/
}
构造方法
public Hashtable() {//有四种构造方法,默认初始大小为11,负载因子为0.75
this(11, 0.75f);
}
添加
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();//hash值是key的hashcode
int index = (hash & 0x7FFFFFFF) % tab.length;//下标index是hash值&第一个是0,后面全为1的值(32位),这一步操作之后能确保是一个正值,接着再对表长度进行取模
@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)) {//如果找到key相同就更新value并返回旧value
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);//如果没有找到key相同就增加一个Entry然后返回null
return null;
}
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {//每次增加之前判定一下是否需要rehash扩容
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);//next==e说明是头插法
count++;
}
删除
public synchronized boolean remove(Object key, Object value) {
Objects.requireNonNull(value);
Entry<?,?> tab[] = table;
int hash = key.hashCode();//hash值是key的hashcode
int index = (hash & 0x7FFFFFFF) % tab.length;//hashToIndex方式以上已经说明
@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) && e.value.equals(value)) {//找到了key、value都相同的值
modCount++;
if (prev != null) {//prev不是链表头结点
prev.next = e.next;
} else {//prev是头结点的情况
tab[index] = e.next;
}
count--;
e.value = null;
return true;
}
}
return false;
}
扩容
protected void rehash() {//每一步添加操作前都要计算以下容量和扩容阈值,超过就要进行扩容
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;//每次扩容是(*2+1)
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);//阈值也要更新
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {//以从后往前的顺序将旧table中元素加入新table里
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
更新和查询
更新
public synchronized boolean replace(K key, V oldValue, V newValue) {//只有满足key,旧value,同时符合时才更新为新的value
Objects.requireNonNull(oldValue);
Objects.requireNonNull(newValue);
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 (; e != null; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
if (e.value.equals(oldValue)) {
e.value = newValue;
return true;
} else {
return false;
}
}
}
return false;
}
public synchronized V replace(K key, V value);//同理这个不管三七二十一直接更新就对了
public synchronized V put(K key, V value);//这个也可以作为更新
查询
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;//hashToIndex
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
public synchronized V getOrDefault(Object key, V defaultValue) {//这个是若key为空就返回默认值
V result = get(key);
return (null == result) ? defaultValue : result;
}
遍历
简单地说一般有五种方法:(1)通过EntrySet返回键值对集合。(2)通过keySet获取Set类型对象的Iterator遍历。(3)通过values获取Collection类型对象的Iterator遍历。(4)通过keys获取枚举类型对象遍历。(5)通过elements获取枚举类型对象对象遍历。详细的就不写了,参考以前的文章和Hashtable源码。
总结
Hashtable实现了线程安全的key-value键值对映射集合,其底层是数组+单链表来解决hash冲突问题,每次插入新的键值对映射前会计算是否超过rehash阈值,超过则进行rehash扩容,扩容是(*2+1),插入是头插法(即在链表头插入新的元素),在修改时是快速失败机制但是在迭代器中快速失败无法保证。如果不需要线程安全的实现,建议使用HashMap代替Hashtable。如果需要线程安全的并发实现,那么建议使用ConcurrentHashMap代替Hashtable。