概述
HashTable和HashMap类似,底层都是使用数组+单向链表实现,只是HashMap中多了一个转化红黑树的过程。HashTable不允许键值为空、遍历时无序、键不可重复、线程安全(在常用方法介绍里面可以看到,基本的操作方法例如put()、remove()等都使用了synchronized同步修饰)。
继承关系
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
- 继承Dictionary类:Dictionary 类是任何可将键映射到相应值的类的抽象父类,Dictionary类声明了操作键值对的接口方法
- 实现Map接口:提供了map操作的一些方法供实现,下面会说到,主要的方法都是实现Map接口中的
- 实现Cloneable接口:为了使用Object.clone()方法,不实现该接口会报错。
- 实现Serializable接口:表示该类可以被序列化
数据结构
//Entry集合,用来存放数据
private transient Entry<?,?>[] table;
//内部类Entry
private static class Entry<K,V> implements Map.Entry<K,V> {
//Entry的hash值
final int hash;
//Entry的键,Key
final K key;
//Entry的value
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;
}
//重写clone()方法
@SuppressWarnings("unchecked")
protected Object clone() {
return new Entry<>(hash, key, value,
(next==null ? null : (Entry<K,V>) next.clone()));
}
//获取key
public K getKey() {
return key;
}
//获取value
public V getValue() {
return value;
}
//设置value
public V setValue(V value) {
//value为空抛出异常
if (value == null)
throw new NullPointerException();
//设置新value返回旧的value
V oldValue = this.value;
this.value = value;
return oldValue;
}
//重写equals比较方法
public boolean equals(Object o) {
//先判断是不是Entry类型,如果不是直接返回false
if (!(o instanceof Map.Entry))
return false;
//如果是Entry类型,先判断键是不是相等,再判断值是不是相等,都相等才返回true
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()));
}
//hash值计算,调用Object的hashCode计算出hash值,再和原本的hash进行异或运算
public int hashCode() {
return hash ^ Objects.hashCode(value);
}
//重写toString方法
public String toString() {
return key.toString()+"="+value.toString();
}
}
成员变量
//Entry类型数组,存放键值对的数据,每个位置上存放的是Entry的首节点
private transient Entry<?,?>[] table;
//当前Entry数组中的数量
private transient int count;
//触发扩容的值,用来判断是否需要进行扩容
private int threshold;
//负载因子,就是允许数组中存放的比例,当数组中元素的个数超过了总容量*负载因子时,触发扩容
private float loadFactor;
//记录修改次数,并发时用于判断是否有其他线程更改了数据
private transient int modCount = 0;
//序列化编号
private static final long serialVersionUID = 1421746759512286392L;
//数组允许的最大长度,等于Integer的最大值-8,和ArrayList一样
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//key键集合,不允许重复
private transient volatile Set<K> keySet;
//键值对集合,不允许重复
private transient volatile Set<Map.Entry<K,V>> entrySet;
//value值集合,允许重复
private transient volatile Collection<V> values;
//静态常量
//迭代器中用到的type,Enumerations、Iterations
private static final int KEYS = 0;
private static final int VALUES = 1;
private static final int ENTRIES = 2;
构造方法
- 入参集合的长度和负载因子
public Hashtable(int initialCapacity, float loadFactor) {
//长度不能小于0
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//负载因子不能小于0,且必须是数字
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
//如果长度=0,把长度设置为1
if (initialCapacity==0)
initialCapacity = 1;
//把负载因子指向loadFactor
this.loadFactor = loadFactor;
//初始化数组
table = new Entry<?,?>[initialCapacity];
//设置threshold 值为数组长度*负载因子-最大值之间的最小值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
- 入参指定参数,负载因子为默认的0.75
//调用上面的构造方法
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
- 无参构造方法,默认长度为11,负载因子为0.75
public Hashtable() {
this(11, 0.75f);
}
- 入参一个Map集合
public Hashtable(Map<? extends K, ? extends V> t) {
//集合长度取tde长度*2-11之间的最大值,负载因子是0.75
this(Math.max(2*t.size(), 11), 0.75f);
//putAll方法下面会详细说
putAll(t);
}
扩容和hash计算
- hash计算
//在put方法中有
int hash = key.hashCode();
//在hash为负值的情况下,以和0x7FFFFFFF进行&操作。0x7FFFFFFF 二进制 0111 1111 1111 1111 1111 1111 1111 1111,负数与其进行&操作将产生一个正整数。
int index = (hash & 0x7FFFFFFF) % tab.length;
- 扩容
//扩容
protected void rehash() {
//获取原数组大小和原数组元素
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
//新的大小=原数组*2+1,即原数组的两倍+1
//HashMap是扩容到原来的两倍,ArrayList是1.5倍
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 的值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//将新的数组指向table
table = newMap;
//循环数组
for (int i = oldCapacity ; i-- > 0 ;) {
//循环数组内的entry元素
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;
}
}
}
常用方法
1.get(Object key)
//通过key获取value
public synchronized V get(Object key) {
//获取原Entry数组
Entry<?,?> tab[] = table;
//得到key的hash值
int hash = key.hashCode();
//计算出key所在的index位置
int index = (hash & 0x7FFFFFFF) % tab.length;
//循环遍历index处的entry
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
//如果有hash相等且key也相等的,返回value值
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
//否则返回null
return null;
}
2.put(K key, V value)
//设置一对键值到集合中
public synchronized V put(K key, V value) {
//value为null抛出异常
if (value == null) {
throw new NullPointerException();
}
//获取元Entry数组
Entry<?,?> tab[] = table;
//计算key的hash值
int hash = key.hashCode();
//通过hash值计算key在table中的index位置
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
//获取entry元素
Entry<K,V> entry = (Entry<K,V>)tab[index];
//如果元素不为空,说明hash碰撞,循环遍历获取到的entry
for(; entry != null ; entry = entry.next) {
//如果key已经存在,覆盖原来的值,返回旧的值
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
//否则调用addEntry添加,在下面
addEntry(hash, key, value, index);
return null;
}
private void addEntry(int hash, K key, V value, int index) {
//增加修改次数
modCount++;
//获取原table
Entry<?,?> tab[] = table;
//如果数组中的元素已经大于threshold
if (count >= threshold) {
//调用rehash扩容,上面说了
rehash();
//重新获取扩容后的table
tab = table;
//计算key的hashCode,并获取其在数组中的index位置
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
//获取当前位置的元素
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
//调用Entry的构造方法,将元素插入为entry首节点,设置table中index的指向
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
3.putAll(Map<? extends K, ? extends V> t)
//内部调用put方法
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());
}
4.contains(Object value)
//判断value是否已经存在
public synchronized boolean contains(Object value) {
//value为空抛出异常
if (value == null) {
throw new NullPointerException();
}
//获取原数组
Entry<?,?> tab[] = table;
//循环遍历,如果找到了相同的value,返回true
for (int i = tab.length ; i-- > 0 ;) {
for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
//否则返回false
return false;
}
5.replace(K key, V oldValue, V newValue)
//替换指定key-value的value
public synchronized boolean replace(K key, V oldValue, V newValue) {
//先做非空判断
Objects.requireNonNull(oldValue);
Objects.requireNonNull(newValue);
//获取原table
Entry<?,?> tab[] = table;
//计算key的hash值,并通过hash获取对应的index
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
//获取index处的元素
Entry<K,V> e = (Entry<K,V>)tab[index];
//如果元素不为空,循环遍历
for (; e != null; e = e.next) {
//如果有hash相等、key相等、value也相等的元素
if ((e.hash == hash) && e.key.equals(key)) {
if (e.value.equals(oldValue)) {
//将旧的元素value替换成新的value,返回true
e.value = newValue;
return true;
} else {
//否则返回false
return false;
}
}
}
//否则返回false
return false;
}
6.remove
- remove(Object key)
//根据指定的key删除元素
public synchronized V remove(Object key) {
//获取原table数据
Entry<?,?> tab[] = table;
//计算key的hash值,通过hash值计算出index
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
//获取index处的元素
Entry<K,V> e = (Entry<K,V>)tab[index];
//循环遍历e,prev用来记录上一个元素,处理删除后的链接
for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
//如果hash值相等,key也相等
if ((e.hash == hash) && e.key.equals(key)) {
//记录修改次数
modCount++;
//判断是否是第一个节点
if (prev != null) {
//如果不是第一个节点,把删除节点的next赋值给删除节点的上一个节点
prev.next = e.next;
} else {
//如果是第一个节点,直接把e的next节点指向table的index位置
tab[index] = e.next;
}
//集合中的key-value数量-1
count--;
//将原节点e的value赋值为null,返回旧的值
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
- remove(Object key, Object value)
//根据指定的key-value删除元素
public synchronized boolean remove(Object key, Object value) {
//做非空判断
Objects.requireNonNull(value);
//步骤和上面的方法是一样的
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) {
//只是这里判断的时候多加了一个value的判断
if ((e.hash == hash) && e.key.equals(key) && e.value.equals(value)) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
e.value = null;
return true;
}
}
return false;
}
7.clear()
//清空集合中的所有元素
public synchronized void clear() {
//获取原table
Entry<?,?> tab[] = table;
modCount++;
//循环遍历,清空table的所有元素
for (int index = tab.length; --index >= 0; )
tab[index] = null;
//设置count
count = 0;
}
8.size()
//返回集合中key-value个数
public synchronized int size() {
return count;
}
9.isEmpty()
//判断集合是否为空
public synchronized boolean isEmpty() {
return count == 0;
}