HashTable源码解析
HashTable
// 业务代码
Hashtable<String,Integer> map = new Hashtable<>();
for (int i = 0; i < 17; i++) {
map.put("add"+i,1);
}
先铺垫一下,认识一个HashTable依赖的类 Entry<K,V>
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;// hash值
final K key;//key值
V value;//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;
}
@SuppressWarnings("unchecked")
protected Object clone() {
return new Entry<>(hash, key, value,
(next==null ? null : (Entry<K,V>) next.clone()));
}
// Map.Entry Ops
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
if (value == null)
throw new NullPointerException();
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
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()));
}
public int hashCode() {
return hash ^ Objects.hashCode(value);
}
public String toString() {
return key.toString()+"="+value.toString();
}
}
put 方法
private transient Entry<?,?>[] table;// 用于存储数据对象的数组
public synchronized V put(K key, V value) {
// Make sure the value is not null. value值不能为null,为null直接抛出异常
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;// 获取table
int hash = key.hashCode();// 计算key的hash值,并赋值给hash(这就是为啥key不能为null的原因)
int index = (hash & 0x7FFFFFFF) % tab.length;//根据hash计算桶位(这个kv键值对应该放置在何处)
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];//获取此桶位的Entry对象
// 从此桶位置开始,循环往后遍历HashTable
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
// 若找到了一个桶,此桶的hash值等于key的hash值,那么就意味着hash冲突了
V old = entry.value;//先将桶里的value值暂存至old
entry.value = value;//再将新值赋给当前桶
return old;//返回旧值,由此put方法结束(新值替换旧值)
}
}
// 若经过for循环都没有找到具备和key的hash相同的hash值得桶,则调用addEntry,将kv对加入到HashTable中
addEntry(hash, key, value, index);
return null;
}
addEntry
private transient int count;
private int threshold;//阈值,在初始化HashTable时赋值
private void addEntry(int hash, K key, V value, int index) {
modCount++;//修改次数+1
Entry<?,?> tab[] = table;//获取HashTable中存储数据对象的数组
if (count >= threshold) {
// HashTable中的元素总数已经大于等于阈值,这时需要重新整理HashTable
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();//因为整理了HashTable,所以重新获取hash值
index = (hash & 0x7FFFFFFF) % tab.length;//根据hash计算桶位(这个kv键值对应该放置在何处)
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];//获取table桶下标为index的对象
tab[index] = new Entry<>(hash, key, value, e);//把kv对赋值到当前桶,并把原来的e作为当前桶的next节点(Entry的构造函数见上面Entry类)
count++;//HashTable中已使用的桶的量+1
}
rehash 整理HashTable
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//2,147,483,639
private float loadFactor;// 0.75,在初始化时赋值
protected void rehash() {
int oldCapacity = table.length;// 获取HashTable中已使用桶的量
Entry<?,?>[] oldMap = table;//获取HashTable
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;//计算扩容大小,旧容量*2 + 1
// 判断扩容后的量是否大于MAX_ARRAY_SIZE
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];//生成一个新Entry数组
modCount++;//修改次数+1
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);//计算阈值=扩容后总量*0.75
table = newMap;//新Entry数组 赋值给table
// 从尾到头遍历旧table的每个桶
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;//计算此Entry对象在新数组中的桶位置
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;//将暂存节点 赋值到新Entry数组的桶中
}
}
}
其实HashTable
的实现方式比HashMap
简单,它仅依靠单向链表+数组的形式实现,且是线程安全的类。
本文来自博客园,作者:勤匠,转载请注明原文链接:https://www.cnblogs.com/JarryShu/articles/18178265
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现