深入理解系列之JAVA数据结构(4)——Hashtable

1、Hashtable和HashMap,从存储结构和实现来讲基本上都是相同的,
Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类,但二者都实现了Map接口。
2、它和HashMap的最大的不同是它是线程安全的,另外它不允许key和value为null。
3、Hashtable是个过时的集合类,不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换!

因为HashMap和HashTable大部分原理都是相同的,所以我们首先做一个表来比较一下二者核心的概念,然后在重点讲述几个点!
这里写图片描述

问题一、数组索引位的确认有何不同?

我们在HashMap中已经讲过,为了避免碰撞几率发生,Hashtable采用了素数作为实际的初始化容量的大小,为了保证扩容后的容量依然是素数,通过*2+1来实现!所以,不必有JDK8的高位运算来参与碰撞优化的机制,所以index = (hash & 0x7FFFFFFF) % tab.length;中的hash是直接的hashcode值:

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

我们知道0x7FFFFFFF = 0111 1111 1111 1111 1111 1111 1111 1111,所以拿hashcode&0x7FFFFFFF就是为了去掉符号位罢了!
从这个源码中还可以发现:
1、另外当出现冲突的时候,HashMap会使用链表+红黑树,但是在Hashtable中就只用链表了,所以效率会有下降!
2、另外可以看到,方法中加了锁synchronized,这也是Hashtable线程安全的原因,也是其效率较低的另一个原因!

问题二、扩容机制有哪些不同?

Hashtable的扩容后会重新遍历所有的节点,然后重新计算新容量下的hash桶索引位然后一个个放置或者链接,其中链接采用头插法,这也就以为着链表数据在重新刷新后会出现倒置现象!先看源码:

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

前面的和HashMap一样就是扩容,从for循环开始进行重新刷新(注意:如果看不懂,可以直接推演一遍)——

1、遍历Hash桶数组,且从尾索引位置开始
2、以此索引位置作为头结点,遍历该索引位下的所有节点;
····> 1、缓存当前节点old->e,并准备下一节点old=old.next
····> 2、计算在新数组下的新索引位置,首先把当前节点e(由old缓存的)采用头插法插入或者接入新索引位置上的数据(如果新位置数据为null,则e直接指向null,否则指向新位置节点)
····> 3、把当前节点直接放到新数组的位置上,即覆盖原来位置上的数据!(因为原来的数据已经由e.next连接起来了,所以不必担心丢失,执行这几步的结果就是头插法,把新的头节点放在心数组索引位置上)!

为了便于理解,我引用网络上的一篇博客来阐述:
假设了我们的hash算法就是简单的用key mod 一下表的大小(也就是数组的长度)。其中的哈希桶数组table的size=2, 所以key = 3、7、5,put顺序依次为 5、7、3。在mod 2以后都冲突在table[1]这里了。这里假设负载因子 loadFactor=1,即当键值对的实际大小size 大于 table的实际大小时进行扩容。接下来的三个步骤是哈希桶数组 resize成4,然后所有的Node重新rehash的过程。
这里写图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值