Hashtable 简介
Hashtable 存储的内容是键值对(key-value)映射,其底层实现是一个Entry数组+链表。
Hashtable是线程安全的它的key、value都不可以为null。此外,Hashtable中的映射不是有序的。且Hashtable大部分方法是用synchronized修饰,证明Hashtable是线程安全的。
Hashtable的继承结构
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable{ //do something}
Hashtable继承于Dictionary类(Dictionary类中声明了操作键值对的方法),实现了Map接口(定义键值对接口);
Hashtable的几个重要成员变量
1.private transient Entry<K,V>[] table; 键值对/Entry数组,每个Entry本质上是一个 单向链表
2.private transient int count; Entry的总数
3.private int threshold:rehash阈值,当count超过该阈值会rehash(重排序)
4.private float loadFactor:负载因子
5.private transient int modCount = 0; //用来帮助实现fail-fast机制,叫做结构修改次数 数组修改一次就加一(改变Entry的结构或修改器内部结构)
6.private transient volatile Set<Map.Entry<K,V>> entrySet:键值对集合,不可重复;
7.private transient volatile Set keySet:key的集合,不可重复;
8.private transient volatile Collection values:value集合,可重复;
注意1:
我们必须要知道Hashtable底层是采用数组+链表的结构来实现的。
注意2:
加载因子loadFactor是Hashtable扩容前可以达到多满的一个尺度。这个参数是可以设置的,默认是0.75。Hashtable的初始化capacity为11。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 get 和 put 操作,都反映了这一点)。
Hashtable几个重要的方法分析
主要方法:Hashtable()、contains()、get()、rehash()、addEntry()、put(K,V)、remove(Object),像size()、keys()、values()、isEmpty()、elements()比较简单就不一一介绍了。
1.Hashtable()方法:
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); //threshold的计算方式,MAX_ARRAY_SIZE防止经过n次扩容后,数组大小超出整数的最大值,所以这里设定一个上限的阈值, MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
initHashSeedAsNeeded(initialCapacity);
}
对初始容量,负载因子,阈值等判断和计算,
2.contains()方法:
public synchronized boolean contains(Object value) {
if (value == null) { //这里,如果value为空,会报NullPointerException
throw new NullPointerException();
}
Entry tab[] = table;
for (int i = tab.length ; i-- > 0 ;) { //从数组的最后往前遍历
for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true; //如果找到value,返回true
}
}
}
return false; //否则,返回false
}
3.containsKey()方法:
public synchronized boolean containsKey(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return true;
}
}
return false;
}
计算index, % tab.length防止数组越界, index表示**key对应entry所在链表表头;**除数取余法进行散列分布
4.get()方法:
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length; //通过key的hash值和table的length,经过运算得到散列表中的索引
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { //遍历tab[index]对应的链表
if ((e.hash == hash) && e.key.equals(key)) { //如果entry的key和hash值与期望值一致,则返回value
return e.value;
}
}
return null; //否则,返回null
}
5.rehash()方法:
protected void rehash() {
int oldCapacity = table.length;
Entry<K,V>[] oldMap = table; //保存旧的容量和Entry数组
// 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<K,V>[] newMap = new Entry[newCapacity];
modCount++; //此时,散列表内的元素发生变化,modCount指针加1
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); //重新计算新的threshold
boolean rehash = initHashSeedAsNeeded(newCapacity);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) { //对散列表内的所有元素进行“重排列”,顺序从后往前
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
if (rehash) {
e.hash = hash(e.key);
}
int index = (e.hash & 0x7FFFFFFF) % newCapacity; // 重新计算每个Entry链表的表头索引(rehash)
e.next = newMap[index]; //开辟新节点
newMap[index] = e;
}
}
}
当Hashtable中键值对总数超过阈值(容量*装载因子)后,内部自动调用rehash()增加容量,重新计算每个键值对的hashCode;
int newCapacity = (oldCapacity << 1) + 1计算新容量 = 2 * 旧容量 + 1;并且根据新容量更新阈值;
6.put()方法:
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 = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<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;
}
}
addEntry(hash, key, value, index); //加入新的键值对
return null;
设置键值对,key和value都不可为null,设置顺序:
1.如果Hashtable含有key,设置(key, oldValue) -> (key, newValue);
2.如果Hashtable不含有key, 调用addEntry(...)添加新的键值对;
6.addEntry()方法:
private void addEntry(int hash, K key, V value, int index)
//如果count大于阈值
if (count >= threshold) {
//进行重排列,rehash操作,调整整个table //如果不大于阈值,则直接插入
这个方法需要注意链接新节点的时候,新的结点是链表表头。