包路径:package java.util;
import java.io.*;
一、基本属性
1、private transient Entry<K,V>[] table; 数组,用来存储hashtable数据
2、 private transient int count; 数组的大小
3、private int threshold; 阈值
4、private float loadFactor; 加载因子
5、private transient int modCount = 0; 版本号
6、private static final long serialVersionUID = 1421746759512286392L;
7、static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
8、transient int hashSeed; 哈希种子
二、继承关系
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable
HashTable继承于Dictionary这一抽象类,查看Dictionay这个抽象类的源码发现,Dictionary直接继承于Object类,没有任何的实现接口,除了构造方法之外全都是抽象方法,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];初始化时创建Entry类型的table数组
用于扩容的临界值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
initHashSeedAsNeeded(initialCapacity);
}
构造方法二、传入一个参数:初始化容量,加载因子取默认的0.75f
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
构造方法三、无参构造方法,初始化容量为11,加载因子取默认的0.75f
public Hashtable() {
this(11, 0.75f);
}
构造方法四、传入Map集合类型的对象t
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
四、核心方法
1、添加元素:put()
public synchronized V put(K key, V value) {
if (value == null) { 当value值为null的时候,抛出异常
throw new NullPointerException();
}
Entry tab[] = table;
int hash = hash(key); 计算key的哈希码
int index = (hash & 0x7FFFFFFF) % tab.length; 求出key的哈希码所对应的索引下标
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
在tab数组对应索引下标的链表下进行查找遍历,是否存在相同的键值key
if ((e.hash == hash) && e.key.equals(key)) {
采用“==”计算哈希码是否相同;采用“.equals()”计算键值key是否相等,需要这两者都满足则说明存在相同的键值key,进行替换操作
V old = e.value;
e.value = value;
return old; 返回旧的value值
}
}
modCount++; 添加元素,tab数组的内部结构发生变化,版本号++
if (count >= threshold) { 如果元素数量大于阈值
rehash(); 扩容并进行重哈希计算
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
Entry<K,V> e = tab[index]; 创建新的entry
tab[index] = new Entry<>(hash, key, value, e); 链接到数组中
count++; 元素数量+1
return null;
}
protected void rehash() { 扩容并计算重哈希过程
int oldCapacity = table.length; HashTable旧的容量
Entry<K,V>[] oldMap = table;
int newCapacity = (oldCapacity << 1) + 1; 新的容量为原来旧的容量的2倍+1
if (newCapacity - MAX_ARRAY_SIZE > 0) {
判断新的容量是否超过最大值,如果超过了,将超出的改为最大值
if (oldCapacity == MAX_ARRAY_SIZE) 如果旧的容量是最大值,则直接返回
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<K,V>[] newMap = new Entry[newCapacity]; 用新的容量创建新的数组
modCount++; 修改次数+1
重新计算阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
根据新的容量重新初始化hashseed
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; 重新计算位置
e.next = newMap[index]; 设置元素
newMap[index] = e; 放入元素
}
}
}
2、获取元素:get()
public synchronized V get(Object key) { 根据键值key获取元素
Entry tab[] = table;
int hash = hash(key); 计算键值key的哈希码
int index = (hash & 0x7FFFFFFF) % tab.length; 计算对应的位置
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
从table数组对应的索引下标的链表处开始进行遍历,查找键值key是否存在,与添加元素比较键值key时所使用的思想有出入!!!
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
3、删除元素:remove()
public synchronized V remove(Object key) { 根据键值key进行删除
Entry tab[] = table;
int hash = hash(key); 计算键值key的哈希码
int index = (hash & 0x7FFFFFFF) % tab.length; 确定所在的位置
遍历找到该元素
for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
modCount++; 修改次数+1
if (prev != null) { 判断不是第一位的,则将元素进行移动
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--; 元素数量-1
V oldValue = e.value;
e.value = null; 将元素置空,并返回旧的值
return oldValue;
}
}
return null;
}
五、总结
1、底层数据结构是数组+双向链表。
2、HashTable的默认初始容量为11,加载因子为0.75。
3、扩容方式是2倍加1的扩容。
4、HashTable所有的方法都是有关键字synchronized修饰线程安全(至于这个关键字后面文章会介绍)。
5、HashTable不管是键值还是值都不可以存储null。