在Java的集合框架中,`Hashtable`是一个历史悠久且线程安全的哈希表实现。尽管在现代开发中,`HashMap`和`ConcurrentHashMap`更为常用,但`Hashtable`依然具有重要的学习价值,尤其是在理解线程安全机制和早期Java集合设计思想方面。本文将对`Hashtable`的源码进行深度剖析,帮助读者掌握其内部实现原理与使用场景。
一、类定义与基本结构
`Hashtable`继承自`Dictionary`抽象类,并实现了`Map`接口、`Cloneable`和`Serializable`接口。其类定义如下:
“`java
public class Hashtable<K,V> extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
“`
这表明`Hashtable`是一个键值对的容器,支持序列化和克隆操作。
二、内部结构
`Hashtable`的底层实现是一个哈希表,使用数组加链表的方式处理哈希冲突(与早期的`HashMap`类似):
“`java
private transient Entry<?,?>[] table;
“`
其中,`Entry`是一个静态内部类,表示哈希表中的一个节点,结构如下:
“`java
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
// …
}
“`
三、线程安全机制
`Hashtable`最显著的特点是它的线程安全性。几乎所有公开的方法都被`synchronized`关键字修饰,例如:
“`java
public synchronized V put(K key, V value) {
// …
}
“`
这意味着在同一时刻,只有一个线程可以执行`put`、`get`等操作,从而避免了多线程下的数据不一致问题。但这种粗粒度的锁机制也带来了性能瓶颈,因此在高并发场景中通常推荐使用`ConcurrentHashMap`。
四、哈希函数与索引计算
`Hashtable`使用对象的`hashCode()`方法作为哈希值,并通过以下方式计算索引:
“`java
int index = (hash & 0x7FFFFFFF) % tab.length;
“`
其中,`0x7FFFFFFF`的作用是将哈希值转换为正数(去掉符号位),然后对数组长度取模,得到插入位置。
五、扩容机制
当元素数量超过容量与负载因子的乘积时,`Hashtable`会进行扩容。默认负载因子是 `0.75`,初始容量是 `11`。扩容时,新容量是原来的 `2n+1`。
“`java
int newCapacity = (oldCapacity << 1) + 1;
“`
扩容会重新哈希(rehash),即重新计算每个元素的位置,因此扩容是一个相对耗时的操作。
1. `put(K key, V value)` 方法
“`java
public synchronized V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException();
}
// 计算哈希值
int hash = key.hashCode();
// 查找是否已存在该键
Entry<?,?> tab[] = table;
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings(“unchecked”)
Entry<K,V> entry = (Entry<K,V>)tab[index];
for (; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
// 添加新节点
addEntry(hash, key, value, index);
return null;
}
“`
可以看到,`put`方法首先检查键或值是否为null(`Hashtable`不允许null键或null值),然后查找是否已存在该键,存在则更新,否则添加新节点。
2. `get(Object key)` 方法
“`java
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;
}
“`
`get`方法同样为`synchronized`,通过哈希值找到索引位置,再遍历链表查找对应的键值对。
七、与HashMap的区别
| 特性 | Hashtable | HashMap |
|——|———–|———|
| 线程安全 | 是 | 否 |
| 允许null键/值 | 否 | 是 |
| 扩容方式 | 2n+1 | 2n(2的幂次) |
| 哈希算法 | 简单取模 | 扰动函数优化 |
| 迭代器 | Enumerator(不支持快速失败) | Iterator(支持快速失败) |
八、总结
`Hashtable`作为Java早期集合框架的重要组成部分,其线程安全的设计和哈希表的实现方式具有一定的历史意义。尽管它在现代并发编程中已被更高效的`ConcurrentHashMap`所取代,但理解其源码依然是掌握Java集合框架原理的重要一环。
通过本文的源码剖析,我们了解了`Hashtable`的内部结构、线程安全机制、哈希计算、扩容策略以及`put`和`get`方法的实现细节。对于初学者而言,深入理解这些内容有助于构建扎实的Java基础。