HashTable源码分析

1、HashTable概述

HashTable以key-value形式存储数据,key不允许为null,value也不允许为null。

HashTable线程安全。

HashTable对大量的方法都添加synchronized锁,所以性能不高。

HashTable底层是数组,又被称为hash桶,每个桶中存放的是链表,链表中的节点存储着集合中的元素。

当HashTable中的元素数量达到threshold阈值的时候,会触发扩容,hash桶的长度翻倍。

单线程场景下可以使用HashMap,多线程场景下可以使用ConcurrentHashMap,HashTable基本上处于被放弃的状态,所以今天这篇文章也就简单介绍一下HashTable,不会像之前文章HashMap源码分析介绍HashMap那样细致。

2、HashTable计算key在hash桶中落点的算法

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

hash & 0x7FFFFFFF 这一步是为了保证hash值是正数。

HashTable在计算key在hash桶中位置的时候直接采用对hash桶大小取余的方式。

相对于HashMap中对hash值采用扰动函数以及与运算的方式,HashTable中key的分布不够均匀并且计算位置效率低。

3、HashTable的构造方法

HashTable一共有四个构造方法:

public Hashtable() {
    this(11, 0.75f);
}


public Hashtable(int initialCapacity) {
    this(initialCapacity, 0.75f);
}

public Hashtable(int initialCapacity, float loadFactor) {
    //校验initialCapacity
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    //校验initialCapacity
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal Load: "+loadFactor);

    //如果initialCapacity == 0 将其设置成1
    if (initialCapacity==0)
        initialCapacity = 1;
    this.loadFactor = loadFactor;
    //根据initialCapacity值构建hash桶
    table = new Entry<?,?>[initialCapacity];
    //计算threshold
    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}

public Hashtable(Map<? extends K, ? extends V> t) {
    //初始化构建HashTable
    this(Math.max(2*t.size(), 11), 0.75f);
    //将参数 t 中的元素一一put到构造出来的HashTable中
    putAll(t);
}

其中前三个构造方法都是初始化一个空的HashTable,第四个构造方法会构造一个HashTable然后将参数中的 t 中的元素一一put到构造出来的HashTable中。

从几构造方法可以看出:
- 默认hash桶长度为11
- 在构造方法执行结束后hash桶就初始化完毕了

由于HashTable采用取余算法来获取key对应hash桶的位置,所以不需要保证hash桶的大小满足 2 ^ n。

4、扩容方法

protected void rehash() {
    int oldCapacity = table.length;
    Entry<?,?>[] oldMap = table;

    //新hash桶大小为当前hash桶大小两倍加一
    int newCapacity = (oldCapacity << 1) + 1;
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        if (oldCapacity == MAX_ARRAY_SIZE)
            // Keep running with MAX_ARRAY_SIZE buckets
            return;
        newCapacity = MAX_ARRAY_SIZE;
    }
    //根据新hash桶大小创建Entry数组
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

    modCount++;
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    table = newMap;

    //遍历当前hash桶,将hash桶中元素一一设置到新hash桶中
    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;
        }
    }
}

这里HashTable的扩容方法很简单,就是先扩容创建新的hash桶,然后将当前hash桶中的元素一一设置到新的hash桶中。

这里需要注意的是扩容是Hash桶的大小为之前hash桶大小的两倍加一,是为了保证扩容后hash桶的大小一定是奇数,据说是为了hash算法对奇数取余可以分散的更加均匀,具体需要去了解一下hash算法。

这里rehash()方法没有加同步是因为调用reshah()方法的方法都被加上了同步,所以扩容不会存在线程问题。

5、常用的方法

这里get、put等方法源码都很好理解,所以就不带大家一起看源码了。

常用的方法如:size()、isEmpty()、get()、put()、remove()、clear()等等都加上了同步,所以HashTable是线程安全的集合。

有一点需要注意的是get()方法对key是null的情况下会抛出空指针;put()方法对key或value是nukk的情况下会抛出空指针,所以HashTable方法的key和value都不允许为空。

6、总结

由于HashTable基本已经被废弃了,所以对于常用方法的源码就不做过多的介绍了。

HashTable是一个线程安全的集合,但是由于性能低所以很少被使用。

性能低体现在对大量方法加同步,锁住整个HashTable,并且对于hash算法和取余算法都没有做优化。


喜欢这篇文章的朋友,欢迎扫描下图关注公众号lebronchen,第一时间收到更新内容。
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值