探索Hashtable底层实现
前言
探索Hashtable底层实现是基于JDK1.8,它的数据结构是数组 + 链表。在不考虑线程是否安全的前提下,它的很多细节处理都不如HashMap,何况如今的HashMap又加了红黑树,查询修改肯定比不上,因为红黑树的时间复杂度是O(logN),而链表的时间复杂度是O(N),新增与删除无法比较,毕竟两者的策略不一致;而倘若比较讨论并发的话,ConcurrentHashMap比它更适合,Hashtable的作者也说了,看来是已经废弃的节奏了。
数据结构
既然是与HashMap类似,那数据结构上肯定比它简单,我们就速速通过!
//可克隆、序列化
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {
//哈希表,负责存储节点
private transient Entry<?,?>[] table;
//哈希表中节点的个数
private transient int count;
/**
* 阈值 = 初始容量 * 加载因子 当超过指定阈值时会对哈希表重新散列,所有节点(链表)重新计算在新表中的索引,这就相当于所有节点都要参与计算、在新表中设置,效率非常地下
* HashMap是先将所有节点分成两部分,最终只需要在新表中设置这两部分即可
*/
private int threshold;
//加载因子,容量大小不变的情况下,加载因子过大减少空间开销,增加查询成本
private float loadFactor;
//结构修改的次数
private transient int modCount = 0;
//包含所有键的迭代器
private transient volatile Set<K> keySet;
//包含所有键值对的迭代器
private transient volatile Set<Map.Entry<K,V>> entrySet;
//包含所有值的迭代器
private transient volatile Collection<V> values;
//最大容量,若设置过高的话可能会发生内存溢出
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//定义键类型,用于迭代器或枚举
private static final int KEYS = 0;
//定义值类型,用于迭代器或枚举
private static final int VALUES = 1;
//定义键值对类型,用于迭代器或枚举
private static final int ENTRIES = 2;
}
构造函数
/**
* 指定初始容量与加载因子构造哈希表
* Float.isNaN:检测是否是数字
* @param initialCapacity 指定初始容量
* @param loadFactor 指定加载因子
*/
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); //设置阈值
}
/**
* 指定初始容量与默认加载因子(0.75)构造哈希表
* @param initialCapacity 指定初始容量
*/
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* 默认初始容量(11)与默认加载因子(0.75)构造哈希表
*/
public Hashtable() {
this(11, 0.75f);
}
/**
* 将指定集合添加到哈希表中,采用默认加载因子
* 设置尽可能大的初始容量以便减少重新散列的次数
* @param m 指定集合
*/
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
简单方法
/**
* 获取哈希表中的元素个数
* @return 哈希表中的元素个数
*/
public synchronized int size() {
return count;
}
/**
* 判断哈希表是否为空
* @return 哈希表是否为空
*/
public synchronized boolean isEmpty() {
return count == 0;
}
/**
* 获取包含所有键的枚举
* @return 包含所有键的枚举
*/
public synchronized Enumeration<K> keys() {
return this.<K>getEnumeration(KEYS);
}
/**
* 获取包含所有值的枚举
* @return 包含所有值的枚举
*/
public synchronized Enumeration<V> elements() {
return this.<V>getEnumeration(VALUES);
}
/**
* 哈希表中是否包含指定值
* @param value 指定值
* @return 是否包含指定值
*/
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;
}
/**
* 哈希表中是否包含指定值
* @param value 指定值
* @return 是否包含指定值
*/
public boolean containsValue(Object value) {
return contains(value);
}
/**
* 哈希表中是否包含指定键
* @param key 指定键
* @return 是否包含指定键
*/
public synchronized boolean containsKey(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
/**
* 0x7FFFFFFF = 0111 1111 1111 1111 1111 1111 1111 1111
* hash & 0x7FFFFFFF 是为了保证结果不出现负数的情况,否则负数取余之后的结果也就为负数了,索引并没有负数
* hash & 0x7FFFFFFF % tab.leng 是为了取在[0, tab.length -1]区间内的索引
*/
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 true;
}
}
return false;
}
/**
* 指定键获取值
* @param key 指定键
* @return 值
*/
@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)) { //hash与equasl都相等才算是相等
return (V)e.value;
}
}
return null;
}
/**
* 扩容并重新散列所有节点
* 新容量 = 旧容量 * 2 + 1
*/
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
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;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) { //将哈希表中的所有节点,包括链表都进行重新散列
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;
}
}
}
/**
* 新增节点
* 采用头插法,每新增一个节点就放到链表的头部
* 节点个数超过阈值时会进行扩容
* @param hash 哈希值
* @param key 指定键
* @param value 指定值
*
*/
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {
// 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);
count++;
}
/**
* 新增节点
* 若发生重复则进行值替换
* @param key 指定键
* @param value 指定值
* @return null或旧值
*/
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;
}
/**
* 指定键移除节点
* @param key 指定键
* @return 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) { //遍历链表 prev:前一个节点 e:当前节点
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;
}
/**
* 批量添加节点
* @param m 指定集合
*/
public synchronized void putAll(Map<? extends K, ? extends V> t) {
for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
put(e.getKey(), e.getValue());
}
/**
* 清空哈希表
*/
public synchronized void clear() {
Entry<?,?> tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )
tab[index] = null;
count = 0;
}
/**
* 浅拷贝
* @return 克隆后的对象
*/
public synchronized Object clone() {
try {
Hashtable<?,?> t = (Hashtable<?,?>)super.clone();
t.table = new Entry<?,?>[table.length];
for (int i = table.length ; i-- > 0 ; ) {
t.table[i] = (table[i] != null)
? (Entry<?,?>) table[i].clone() : null;
}
t.keySet = null;
t.entrySet = null;
t.values = null;
t.modCount = 0;
return t;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
/**
* 根据类型获取枚举
* @param type 类型
* @return 枚举
*/
private <T> Enumeration<T> getEnumeration(int type) {
if (count == 0) {
return Collections.emptyEnumeration();
} else {
return new Enumerator<>(type, false);//false不允许删除
}
}
/**
* 根据类型获取迭代器
* @param type 类型
* @return 迭代器
*/
private <T> Iterator<T> getIterator(int type) {
if (count == 0) {
return Collections.emptyIterator();
} else {
return new Enumerator<>(type, true);//true允许删除
}
}
/**
* 获取包含所有键的迭代器
* @return 包含所有键的迭代器
*/
public Set<K> keySet() {
if (keySet == null)
keySet = Collections.synchronizedSet(new KeySet(), this);//线程安全
return keySet;
}
/**
* 获取包含所有键值对的迭代器
* @return 包含所有键值对的迭代器
*/
public Set<Map.Entry<K,V>> entrySet() {
if (entrySet==null)
entrySet = Collections.synchronizedSet(new EntrySet(), this);
return entrySet;
}
/**
* 获取包含所有值的迭代器
* @return 包含所有值的迭代器
*/
public Collection<V> values() {
if (values==null)
values = Collections.synchronizedCollection(new ValueCollection(), this);
return values;
}
//节点对象
private static class Entry<K,V> implements Map.Entry<K,V> {
//当前节点的哈希值
final int hash;
//当前节点的键
final K key;
//当前节点的值
V value;
//当前节点的下一个节点
Entry<K,V> next;
}
//有些方法并没有展示,请参考HashMap
总结
-
Hashtable的键值对不允许为空,因为它是直接拿这个键去获取哈希值,这不就造成空指针了(感觉有点白痴),它还对值做了空指针判断。
-
Hashtable默认初始容量11、默认加载因子0.75。
-
Hashtable扩容时以
2倍 + 1进行增长,旧表中的所有节点重新散列到新表中,效率较为低下。 -
Hashtable计算索引时采用的是取余,而HashMap采用的与运算。
-
Hashtable新增节点时采用的头插法。
-
Hashtable计算hash值的方式可能会出现高位不同低位相同的两个不同数造成最终的索引相同,相比hashMap,它的hash计算方式降低了碰撞的概率。
重点关注
键值对不允许null 计算索引采用取余 头插法
浙公网安备 33010602011771号