文章目录
一、Hashtable简介
1.Hashtable是什么
与HashMap一样,都是哈希表,以键值对形式存储数据。
2.Hashtable有什么特点
不允许key值和value值为null
与HashMap不同的是,Hashtable是线程安全的,它很多API都加了synchronized 修饰符。正因为如此,多个线程竞争同一把锁,导致效率很低。
3.Hashtable有什么缺点
二、Hashtable源码
1.Hashtable结构
通过源码来分析Hashtable的结构。
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
//Hashtable的数据载体
private transient Entry<?,?>[] table;
//Hashtable长度
private transient int count;
//容量阈值,如果超过这个阈值会通过rehash方法扩容
private int threshold;
//负载因子
private float loadFactor;
//修改Hashtable的次数,用于快速失败机制
private transient int modCount = 0;
}
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;
}
}
由上述代码可以看出:
(1)Hashtable继承了Dictionary类,实现了Map, Cloneable, Serializable接口。
(2)Hashtable是以单链表Entry为基本元素的数组。
2.Hashtable的初始化
/**
* 根据给定的容量和负载因子构建Hashtable
* initialCapacity 容量
* loadFactor 负载因子
*/
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);
//如果负载因子等于零,则取1
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
//初始化Entry数组
table = new Entry<?,?>[initialCapacity];
/**
* 初始容量乘以负载因子得出的值与MAX_ARRAY_SIZE + 1相比较,取较小者;
* 其中,MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
* 这是能分配给数组的最大长度。一些虚拟机保留对象头信息,分配一部分字节用于存储对象头。
*/
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构建Hashtable,使得Hashtable的映射关系与Map相同。
*/
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
3.put方法
public synchronized V put(K key, V value) {
//不允许value为空
if (value == null) {
throw new NullPointerException();
}
//确定key不存在Hashtable中
Entry<?,?> tab[] = table;
//如果key为null,则报空指针异常
int hash = key.hashCode();
/**
* 计算索引,hash & 0x7FFFFFFF是为了当hash值为负数时转成正数
* 因为用的是取余运算,比HashMap计算索引的与运算(n - 1) & hash效率低。
* /
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
//如果存在相同hash值和key的结点,则该结点更新value值
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
//如果不存在相同hash值和key的结点,则新增结点
addEntry(hash, key, value, index);
return null;
}
private void addEntry(int hash, K key, V value, int index) {
//Hashtable修改次数加一
modCount++;
Entry<?,?> tab[] = table;
//如果Hashtable长度大于等于容量阈值,则扩容
if (count >= threshold) {
//扩容,下面会详细介绍
rehash();
tab = table;
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);
//Hashtable长度加一
count++;
}
4.get方法
/*
* 根据key和hash值找到相应的结点,并返回结点的value
*/
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
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;
}
}
return null;
}
5.resize方法
protected void rehash() {
//旧表长
int oldCapacity = table.length;
//旧表
Entry<?,?>[] oldMap = table;
//旧表长乘2加1后赋值给新表容量newCapacity
int newCapacity = (oldCapacity << 1) + 1;
//如果新表容量大于MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
if (newCapacity - MAX_ARRAY_SIZE > 0) {
/**
* 如果旧表长等于MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,则不需要扩容
* 否则,新表容量取MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
*/
if (oldCapacity == MAX_ARRAY_SIZE)
return;
newCapacity = MAX_ARRAY_SIZE;
}
//以新表容量重新初始化一个新表
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
//修改次数加一
modCount++;
//新表容量乘以负载因子后与MAX_ARRAY_SIZE + 1比较,取较小值作为新的容量阈值
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;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
三、额外知识点
1.HashMap和Hashtable有什么区别?
(1)HashMap继承AbstractMap,而Hashtable继承Dictionary
(2)HashMap允许key和value为空,而Hashtable不允许
(3)HashMap线程不安全,而Hashtable线程安全,因为加了synchronized修饰符
关于HashMap线程不安全可以阅读这篇博客(http://www.importnew.com/21396.html)
(4)HashMap默认容量是16,而Hashtable默认容量是11
(5)HashMap容量是2的整数次幂,而Hashtable容量可以是任意正整数
(6)HashMap计算索引的方式是与运算,而Hashtable是取余运算,效率较低
(7)HashMap扩容后是原容量的2倍,而Hashtable扩容后是原容量的2倍+1