HashTable
底层数据结构
HashTable
与 HashMap
一样,都是以键值对的形式存储数据。JDK1.8
中 HashMap
的底层结构是数组 + 链表 + 红黑树,而 HashTable
的底层结构是数组 + 链表
HashTable
源码
HashTable
变量
public class Hashtable<K,V> extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
// 键值对 Entry 数组,每个 Entry 本质上是一个单向链表的表头
private transient Entry<?,?>[] table;
// 当前表中的Entry数量,如果超过了阈值,就会扩容,即调用rehash方法
private transient int count;
// rehash阈值
private int threshold;
// 负载因子
private float loadFactor;
// 用来实现"fail-fast"机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行
// 迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出
// ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你(你已经出错了)
private transient int modCount = 0;
}
HashTable
构造器
// 无参构造器
public Hashtable() {
this(11, 0.75f);
}
// 明确指定容量的构造器
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
// 明确指定容量和负载因子的构造器
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);
}
HashTable
的 get()
方法
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;
}
HashTable
的 put()
方法
public synchronized V put(K key, V value) {
// 值不允许为null
if (value == null) {
throw new NullPointerException();
}
Entry<?,?> tab[] = table;
// 得到键的 hash 值
int hash = key.hashCode();
// 得到对应 hash 值在数组中的桶索引
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
// 得到桶中链表头节点
Entry<K,V> entry = (Entry<K,V>)tab[index];
// 从头开始遍历
for(; entry != null ; entry = entry.next) {
// 一旦 hash值 相等并且键相等,替换旧值
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
// 如果没有找到相同键,那么添加新节点
addEntry(hash, key, value, index);
return null;
}
HashTable
的 remove()
方法
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
// 计算hash值
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) {
// 如果匹配,修改节点
if ((e.hash == hash) && e.key.equals(key)) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
在 jdk 1.5
之前,在并发环境下想要使用 Map
集合,需要使用 HashTable
,因为 HashTable
是线程安全的,但是 HashTable
的安全策略过于简单粗暴,是将所有的方法都加上了 synchronized
关键字,在竞争激烈的并发场景中性能就会非常差
在 jdk 1.5
的时候,增加了并发包 JUC
,里面有许多的并发容器,其中就有ConcurrentHashMap
,在并发情况下保证了线程安全,同时提供了更高的并发效率
ConcurrentHashMap
详情可以查看:https://blog.csdn.net/weixin_38192427/article/details/112941288
HashTable
与 HashMap
的区别
- 底层的数据结构:
HashTable
是数组 + 链表,而HashMap
是数组 + 链表 + 红黑树 - 默认的初始容量:
HashTable
是11
,而HashMap
是16
- 扩容机制:
HashTable
扩容后的大小为原来的2
倍 +1
,而HashMap
是原来大小的2
倍 - 数组的懒加载:
Hashtable
在初始化时就创建了数组,HashMap
对底层数组采取的懒加载,即当执行第一次插入时才会创建数组 - 线程安全:
HashTable
是线程安全的,而HashMap
是线程不安全的 - 键和值是否允许为
null
:HashTable
不允许,HashMap
中键和值均允许为null