集合源码学习(十):HashTable(Java8)与HashMap比较

什么是HashTable

一句话介绍,一个里面方法大部分都是线程安全的集合,类似于HashMap。也是通过一个数组,利用hash函数,如果冲突就用链表进行连接。
具体如下图:
这里写图片描述
当然这只是一句话简短介绍,面试的时候,经常会被问到,
HashTable和HashMap有什么区别?
经常的回答就是,

  • HashTable是线程安全,里面方法大部分是synchronized,而HashMap不是;
  • HashMap里面key和value可以为null,而HashTable中不允许为null;

    在没细致分析集合源码之前,我也是这样认为的,在面试中也是这样回答的(逃

看完两个源码后,发现自己了解的真实太浅了。
有兴趣可以看我前一篇分析HashMap的文章:
集合源码学习(七):HashMap(Java8)

首先看HashTable的定义:

public class HashTable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable 

继承自Dictionary,Dictionary是一个抽象父类,功能和Map一样,但已经是个过时的类了,官方推荐用实现Map接口来取代。
并且实现了Map接口,以及Cloneable,Serializable接口。

接下来的文章,我就以和HashMap比较为主进行讲解。

null值问题

HashTable键(key)和值(value)均不能为null。
先看put方法:

    /**
     * 将key和value加入到map中,明显标明,
     * value不能为null。如果key为null,则会包nullPointer
     */
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }
        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        //很直接的利用hashcode去除table.length,然后取长度。
        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)) {
                //hash相同且equals,那么就连在后面,是用链表的方式。
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
        //第一个,链表后面没有数据。
        addEntry(hash, key, value, index);
        return null;
    }

关于value,明显有if判断,不能为null,
如果key为null,则也直接在计算hashCode的时候就会报空指针。

计算table数组索引值方法

相信大家应该还记得,java8中HashMap更改了hash()方法:
HashMap中:

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

接着计算数组索引:

tab[i = (n - 1) & hash]

而在HashTable中:

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

很直接的利用hashcode去除table.length,然后取长度。

HashTable是线程安全

因为HashTable中大部分方法都是加了synchronized关键字,所以同一时刻,只能有一个线程进入其方法故是线程安全的,这里就不多说。

initialCapacity和loadFactor

有些读者可能还不懂这两个参数的意思,initialCapacity是初始容量,而loadFactor是加载因子,当当前已用大小size=table.length*loadFactory时,就会进行扩容操作。具体可以看我的HashMap分析篇。
说重点,没有指定初始容量时
HashMap中:initialCapacity=16,loadFactory=0.75
HashTable中:initialCapacity=11,loadFactory=0.75

只有链表方式解决冲突

Java8,HashMap中,当出现冲突时,

  • 如果冲突数量小于8,则是以链表方式解决冲突。
  • 而当冲突大于等于8时,就会将冲突的Entry转换为红黑树进行存储。
  • 而又当数量小于6时,则又转化为链表存储。

而在HashTable中,
则都是以链表方式存储。

扩容的额度

Java8中,
HashMap:
HashMap一旦扩容,都是扩展到2的倍数,因为这样有利于计算数组索引值,即和计算数组索引结合起来。就算指定大小,也是会选择一个大于大小的2的倍数。

HashTable:
一次性扩展为oldCapacity*2+1。
先看代码:

  /**
     * 一次扩展是,old*2+1
     */
    @SuppressWarnings("unchecked")
    protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        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;
        }
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;
        //新的threshold值。取newCapacity*loadFactor的小值。
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;

        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;
            }
        }
    }

先扩展,再把旧数组里面元素一个一个加到新的里面。
注意,这里取hash不是e.hash,而仍然是key.hashCode计算保留下来的值。

如果哪里分析有问题或者哪些地方没有分析到的,可以在下方留言。
另附上:
集合源码学习(七):HashMap(Java8)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值