(二)Java集合专题-详细分析HashtableJDK1.8集合底层实现的思想

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_36520235/article/details/83719332

(一)详细分析Hashtable1.8集合底层实现的思路和源码

(1)先来看一下Hashtable的结构图:首先他和HashMap结构都是一样的,都是由数组和链表进行实现的(在JDK1.8是用数组和链表和红黑树的),每一个数组里面存的是一个Entry节点,节点里面有Key、Value,唯一最大 的区别就是HashMap实现不是线程安全的,但是Hashtable是线程安全的,但是Hashtable在多线程下的效率不是很好,比起ConcurrentHashMap的效率比起来还是有点低的。所以不建议使用,这里只做了解就行了
在这里插入图片描述
Hashtable继承的是Dictionary,实现的是Map的接口、Cloneable、和Serializable 的序列化接口

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

(2)他的主要方法都是通过Synchronize来实现的:

public synchronized int size(){}
public synchronized boolean isEmpty() {}
public synchronized Enumeration<K> keys() {}
public synchronized Enumeration<V> elements() {}
public synchronized boolean contains(Object value) {}
public synchronized boolean containsKey(Object key) {}
public synchronized V get(Object key) {}
public synchronized V put(K key, V value) {}
public synchronized V remove(Object key) {}
public synchronized void putAll(Map<? extends K, ? extends V> t) {}
public synchronized void clear() {}
public synchronized Object clone() {}
public synchronized String toString() {}
public synchronized boolean equals(Object o) {}
public synchronized int hashCode() {}

(3)看一下几个主要的源码的实现和初始化的源码:

    1. Hashtable的一些变量参数:
public class Hashtable<K,V> extends Dictionary<K,V> implements 
                    Map<K,V>, Cloneable, java.io.Serializable {
    
    // 存储元素的table,Entry节点采用内部类实现
    private transient Entry[] table;

    // 已存放元素的计数器
    private transient int count;
    
    // 扩容阈值=table长度 * loadFactor
    // table表的元素(不包含链表中的元素)超过这个阈值将扩容
    private int threshold;
    
    // 负载因子
    private float loadFactor;
    
    // 其他省略
}

    1. Hashtable的初始化:在默认构造方法中,调用了Hashtable(int initialCapacity, float loadFactor)方法,初始Hashtable的容量为11,负载因子为0.75,那么初始阈值就是8。这点和HashMap很不同,HashMap在初始化时,table的大小总是2的幂次方,即使给定一个不是2的幂次方容量的值,也会自动初始化为最接近其2的幂次方的容量
    public Hashtable(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);

        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        table = new Entry<?,?>[initialCapacity];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }
    1. 内部类Entry节点:Entry节点中存储了元素的hash值,next是指链表的下一个节点
private static class Entry<K,V> implements Map.Entry<K,V> {
    int hash;
    K key;
    V value;
    Entry<K,V> next;// 链接的下一个节点,构成链表
}
    1. get()方法的源码实现:
    public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

  1. 第一步先对Hashtable的key进行拿到hashcode,
  2. 然后用key的hashcode值进行疑惑,然后对数组的长度进行取余,这里效率没有HashMap的效率高
  3. 然后再去判断当前的这个Entry节点有没有存在数据值,3.1 如果存在,就去判断这个Entry节点的key是不是和等于你要取的值对应的按个key,若果是直接返回
  4. 如果刚刚的那个3.1哪一步判断不存在,就说明没有你要的值,就直接返回NULL
  • 然后是Hashtable的put方法的实现:
    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;
    }

这个是Put方法中的addEntry()方法

    private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        Entry<?,?> tab[] = table;
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();

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

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

这个是addEntry()方法中的rehash()方法

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

这里重点说一下Hashtable中的put()方法的思路;

1、禁止null value插入。
2、根据key的hashCode值 与 0x7FFFFFFF求与后得到新的hash值,然后计算其在table中的索引下标。
3、在索引下标下遍历链表,查找key是否已存在,存在则更新value值。
4、如果不存在,判断table.count是否超过阈值,超过则重新rehash,将原元素全部拷贝到新的table中,并重新计算索引下标。rehash后,容量是以前的2倍+1的大小,这点也和HashMap不同,HashMap是2倍。
5、插入元素时直接插入在链表头部。
6、更新元素计数器。

最后终结一下HashMap和Hashtable的不同之处?

  1. 首先第一点是HashMap不是线程安全的,但是Hashtable是线程安全的
  2. HashMap的key和value都可以为NULL,但是Hashtable的key和value都不能为NULL,否则直接报错。
  3. 在HashMap中的put()方法中,在定位桶数组的下标的时候,使用的是key的hashcode &(n - 1) 跟通数组的长度进行与的位运算,但是在Hashtable中的时候,用到的是(hash & 0x7FFFFFFF) % tab.length;这是对桶数组的长度进行取模,效率肯定没有上一步的hashmap的效率高
  4. HashMap的初始化默认桶数组的长度是16负载因子0.75,但是在Hashtable中的通数组的默认长度是11负载因子是0.75,在进行扩容的时候也会有点不一样,在Hashtable中的扩容时用的是(oldCapacity << 1) + 1(也就是把原来的桶数组长度左移一位二进制,也就是长度*2+1)来进行的,而在HashMap中是直接二倍进行扩容的。
  5. 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。

猜你喜欢

转载自blog.csdn.net/qq_36520235/article/details/83719332
今日推荐