1、Hashtable-源码分析-底层数据结构
1、Hashtable-源码分析-底层数据结构:与HashMap底层数据结构一样、
2、Hashtable-源码中属性字段:
/**
* 键值对/Entry数组,每个Entry本质上是一个单向链表的表头
*/
private transient Entry<?, ?>[] table;
/**
* 当前表中的Entry数量,如果超过了阈值,就会扩容,即调用rehash方法
*/
private transient int count;
/**
* rehash阈值
*
* @serial
*/
private int threshold;
/**
* 负载因子
*
* @serial
*/
private float loadFactor;
/**
* 用来实现"fail-fast"机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行
* 迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出
* ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你(你已经出错了)。
*/
private transient int modCount = 0;
2、Hashtable-源码分析-构造方法
1、Hashtable-源码分析-构造方法:有4个构造方法
2、源代码:
//默认构造函数,指定的容量大小是11;加载因子是0.75
public Hashtable() {
this(11, 0.75f);
}
//指定容量大小的构造函数,加载因子是0.75
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
//指定容量大小和加载因子的构造函数
public Hashtable(int initialCapacity, float loadFactor) {
//验证初始容量,初始容量不能小于0
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,获得大小为initialCapacity的table数组
table = new Entry<?,?>[initialCapacity];
//计算阀值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
//构造一个与给定的 Map 具有相同映射关系的新哈希表,容量是原来容量的两倍和11的最大值。
public Hashtable(Map<? extends K, ? extends V> t) {
//设置table容器大小,其值==t.size * 2 + 1 Vs 11的最大值
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
3、Hashtable-源码分析-Entry类
1、Entry类,与其他Map实现类一样,Hashtable中Entry类实现了Map.Entry<K,V>。
2、源代码:
/**
* Hashtable bucket collision list entry
* 哈希表bucket冲突列表项
* Hashtable的Entry节点,它本质上是一个单向链表。
*/
private static class Entry<K,V> implements Map.Entry<K,V> {
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;
}
@SuppressWarnings("unchecked")
protected Object clone() {
return new Entry<>(hash, key, value,
(next==null ? null : (Entry<K,V>) next.clone()));
}
// Map.Entry Ops
public K getKey() {
return key;
}
public V getValue() {
return value;
}
// 进行判断value是否为空,即不允许value为空,其实key也不能为空
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==null ? e.getKey()==null : key.equals(e.getKey())) &&
(value==null ? e.getValue()==null : value.equals(e.getValue()));
}
public int hashCode() {
// 直接用hash进行异或,与HashMap不同
return hash ^ Objects.hashCode(value);
}
public String toString() {
return key.toString()+"="+value.toString();
}
}
4、Hashtable-源码分析-添加元素
1、Hashtable-源码分析-添加元素:.put()方法
2、源代码:
//添加元素,要求key和value都不能为null
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
/*
* 确保key在table[]是不重复的
* 处理过程:
* 1、计算key的hash值,确认在table[]中的索引位置
* 2、迭代index索引位置,如果该位置处的链表中存在一个一样的key,则替换其value,返回旧值。
*/
Entry<?,?> tab[] = table;
//计算key的hash值
int hash = key.hashCode();
//确认该key的索引位置
//&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而 hash & 0x7FFFFFFF 后,只有符号外改变,而后面的位都不变。
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
//迭代,寻找该key,替换
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
//执行到这里,说明key没有重复,则进行插入操作
addEntry(hash, key, value, index);
//如果是插入全新的Entry的话,就返回空
return null;
}
/**
进行插入操作
*/
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;
//计算key的索引
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);
//容器中元素+1
count++;
}
3、.put()方法,执行过程:
3-1、计算key的hash值,根据hash值获得key在table数组中的索引位置,然后迭代该key处的Entry链表(我们暂且理解为链表),若该链表中存在一个这个的key对象,那么就直接替换其value值即可,否则在将改key-value节点插入该index索引位置处。
4、.put()方法,特殊点:
4-1、在put方法中,如果需要向table[]中添加Entry元素,会首先进行容量校验,如果容量已经达到了阀值,HashTable就会进行扩容处理rehash()。
5、Hashtable-源码分析-扩容方法
1、Hashtable-源码分析-扩容方法:.rehash();
2、源代码:
/**
在put方法中,如果需要向table[]中添加Entry元素,会首先进行容量校验,如果容量已经达到了阀值,HashTable就会进行扩容处理rehash()。
*/
@SuppressWarnings("unchecked")
protected void rehash() {
//旧容量=链表的长度
int oldCapacity = table.length;
//获取旧链表,将老数组赋值给oldMap
Entry<?,?>[] oldMap = table;
// overflow-conscious code
//新容量=旧容量 * 2 + 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;
}
//新建一个size = newCapacity 的HashTable
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
//重新计算阀值:在新容量*加载因子和最大值 + 1之间取最小值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
//将原来的元素拷贝到新的HashTable中
//双重遍历老数组,第一重遍历是遍历桶上的各个Entry,将老数组的节点移到新数组
for (int i = oldCapacity ; i-- > 0 ;) {
//第二重遍历是遍历桶上Entry后面接的链表
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
//将old赋值给e,e代表遍历到的节点
Entry<K,V> e = old;
//old指向他的后继节点,下次遍历就遍历到后继节点了
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
//把新桶上已存在链表节点作为当前节点的后继节点
e.next = (Entry<K,V>)newMap[index];
//遍历到的节点放到新数组的桶上,[!!!]是链表的头结点
newMap[index] = e;
}
}
}
6、Hashtable-源码分析-获取元素
1、Hashtable-源码分析-获取元素:.get()方法
2、源代码:
@SuppressWarnings("unchecked")
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
//桶的索引是用key的hash值和0x7FFFFFFF做与运算,最后对数组长度取余
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 (V)e.value;
}
}
//如果是插入全新的Entry的话,就返回空
return null;
}
3、.get()方法-执行过程:
3-1、计算key的hash值,判断在table数组中的索引位置,然后迭代链表,匹配直到找到相对应key的value,若没有找到返回null。
7、Hashtable-源码分析-删除元素
1、Hashtable-源码分析-删除元素:.remove()方法
2、源代码:
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
//计算key的hash
int hash = key.hashCode();
//根据hash计算桶的索引index
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) {
//如果key的hash相等,而且key本身也相等,说明就是要删掉这个节点
if ((e.hash == hash) && e.key.equals(key)) {
//自增
modCount++;
//如果prev不为空,说明要删除的不是头结点
if (prev != null) {
//那么将目标节点的头结点指向目标节点的后继节点
prev.next = e.next;
} else {
//如果删除的就是头结点,那么将原来头结点的后继节点指定为头结点
tab[index] = e.next;
}
//元素总数 - 1
count--;
//取出要删除的节点的value
V oldValue = e.value;
//将e的value置空(需要吗?)
e.value = null;
//返回删除节点的value
return oldValue;
}
}
return null;
}
8、Hashtable-源码分析-遍历元素
1、Hashtable-源码分析-遍历元素:方法有很多种,此处只介绍Enumeration方式
2、Enumeration类-源代码:
/**
* Enumerator的作用是提供了通过elements()遍历Hashtable的接口和通过entrySet()遍历Hashtable的接口。因为,它同时实现了 Enumerator接口和Iterator接口。
*/
private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
// 指向Hashtable的table
Entry<?, ?>[] table = Hashtable.this.table;
// Hashtable的总的大小
int index = table.length;
Entry<?, ?> entry;
Entry<?, ?> lastReturned;
int type;
/**
* Enumerator是 迭代器(Iterator) 还是 枚举类(Enumeration)的标志
* iterator为true,表示它是迭代器;否则,是枚举类。
*/
boolean iterator;
/**
* 在将Enumerator当作迭代器使用时会用到,用来实现fail-fast机制。
*/
protected int expectedModCount = modCount;
Enumerator(int type, boolean iterator) {
this.type = type;
this.iterator = iterator;
}
/**
* 从遍历table的数组的末尾向前查找,直到找到不为null的Entry。
*/
public boolean hasMoreElements() {
Entry<?, ?> e = entry;
int i = index;
Entry<?, ?>[] t = table;
/* Use locals for faster loop iteration */
while (e == null && i > 0) {
e = t[--i];
}
entry = e;
index = i;
return e != null;
}
/**
* 获取下一个元素
* 注意:从hasMoreElements() 和nextElement() 可以看出Hashtable的elements()遍历方式
* 首先,从后向前的遍历table数组。table数组的每个节点都是一个单向链表(Entry)。
* 然后,依次向后遍历单向链表Entry。
*/
@SuppressWarnings("unchecked")
public T nextElement() {
Entry<?, ?> et = entry;
int i = index;
Entry<?, ?>[] t = table;
/* Use locals for faster loop iteration */
while (et == null && i > 0) {
et = t[--i];
}
entry = et;
index = i;
if (et != null) {
Entry<?, ?> e = lastReturned = entry;
entry = e.next;
return type == KEYS ? (T) e.key : (type == VALUES ? (T) e.value : (T) e);
}
throw new NoSuchElementException("Hashtable Enumerator");
}
// 迭代器Iterator的判断是否存在下一个元素
// 实际上,它是调用的hasMoreElements()
public boolean hasNext() {
return hasMoreElements();
}
// 迭代器获取下一个元素
// 实际上,它是调用的nextElement()
public T next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return nextElement();
}
// 迭代器的remove()接口。
// 首先,它在table数组中找出要删除元素所在的Entry,
// 然后,删除单向链表Entry中的元素。
public void remove() {
if (!iterator)
throw new UnsupportedOperationException();
if (lastReturned == null)
throw new IllegalStateException("Hashtable Enumerator");
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
synchronized (Hashtable.this) {
Entry<?, ?>[] tab = Hashtable.this.table;
int index = (lastReturned.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) {
//当前元素即为上一个返回元素
if (e == lastReturned) {
modCount++;
expectedModCount++;
//删除上一个元素
if (prev == null)
tab[index] = e.next;
else
prev.next = e.next;
count--;
lastReturned = null;
return;
}
}
throw new ConcurrentModificationException();
}
}
}
3、Demo
//通过Enumeration来遍历Hashtable
Enumeration<String> enu = table.keys();
while(enu.hasMoreElements()) {
System.out.println("Enumeration:"+table.keys()+" "+enu.nextElement());
}