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简单,它仅依靠单向链表+数组的形式实现,且是线程安全的类。

posted @   勤匠  阅读(2)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示