基本介绍
- 存放的元素是键值对,即key-value;
- Hashtable的使用方法基本上和HashMap一样;
- Hashtable的键和值都不能为null;
- 不同于HashMap Hashtable是线程安全的(synchronized);
public synchronized V put(K key, V value) {
...
}
简述Hashtable的底层原理
($表示内部类的关系)
- 底层维护了Hashtable$Entry[] 数组,初始化大小为11,临界值为8
- 临界值(threshold) = 数组大小*加载因子(loadFactor:0.75)
public Hashtable() {
//初始容量11,扩容因子0.75
this(11, 0.75f);
}
不同于HashMap在第一次添加时调用扩容方法,HashTable在创建时就指定了初始容量(这个方式和LinedHashSet一样)
put方法
//key重复则执行value值的更新,key不存在则正常添此加键值对。
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
//value不允许为null,null时会抛出异常
throw new NullPointerException();
}
Entry<?, ?> tab[] = table;
int hash = key.hashCode();
//计算此key的索引值index
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K, V> entry = (Entry<K, V>) tab[index];//获得该桶中的头节点
//依次遍历此hash值链表中的节点
for (; entry != null; entry = entry.next) {
//比对是否有 同hash 同key 的重复节点
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
//如果存在重复的key,则进行值覆盖
entry.value = value;
return old;
}
}
//如果此key不是重复元素,则调用函数进行节点添加
addEntry(hash, key, value, index);
return null;
}
//put()方法的具体实现
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;//tab:记录扩容后的数组引用
hash = key.hashCode();//更新key的hash值
index = (hash & 0x7FFFFFFF) % tab.length;//更新索引下标
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K, V> e = (Entry<K, V>) tab[index];//e:此桶中的头节点
//新节点作为头节点,此处选择的是头插法
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
不同于HashMap使用hash()方法计算hash值,HashTable 直接使用 Object.hashCode() 方法计算hash值;
扩容机制
- 执行方法 addEntry(hash, key, value, index); 添加K-V,封装到Entry对象中;
- 当 if (count >= threshold) 满足时,就进行扩容;
- 按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容(当前容量*2+1);
//扩容方法
protected void rehash() {
int oldCapacity = table.length;//记录原数组长度
Entry<?,?>[] oldMap = table;//记录原数组的地址索引
// overflow-conscious code
//新数组长度 = 原数组的2倍+1
//而HashMap中,扩容为原来的2倍:newCap = (oldCap << 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创建一个新数组
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
//更新 扩容阈值threshold
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;
//以同样的方式,通过对新数组长度取余的方式计算新索引index
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;//将数据存放到新数组中
}
}
}
和HashMap对比容易忽略的一些区别
-
- 继承父类不同
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
...
}
public class Hashtable<K,V> extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
...
}
-
- hash值计算不同
HashMap
//经过计算,返回 key的hash值
static final int hash(Object key) {
int h;
//如果key!=null,借助hashCode()方法,计算key的hash值。
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashTable
int hash = key.hashCode();
-
- 下标的计算不同
HashMap
//index = (table.length - 1) & hash;
i = (n - 1) & hash
HashTable
//获得 key的hash值
int hash = key.hashCode();
//计算此 key的索引值index
int index = (hash & 0x7FFFFFFF) % tab.length;
-
- 添加元素的方式
HashMap JDK1.7用的是头插法,JDK1.8用的是尾插法
(详情看HashMap.putVal()方法)
HashTable中用的是头插法
Entry<K,V> e = (Entry<K,V>) tab[index];//e:此桶中的头节点
//将新加节点作为头节点,此处选择的是头插法
tab[index] = new Entry<>(hash, key, value, e);