HashTable源码剖析~

包路径:package java.util;

                import java.io.*;

一、基本属性

1、private transient Entry<K,V>[] table; 数组,用来存储hashtable数据

2、 private transient int count;  数组的大小

3、private int threshold;   阈值

4、private float loadFactor;  加载因子

5、private transient int modCount = 0;  版本号

6、private static final long serialVersionUID = 1421746759512286392L;

7、static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

8、transient int hashSeed;  哈希种子

二、继承关系

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

HashTable继承于Dictionary这一抽象类,查看Dictionay这个抽象类的源码发现,Dictionary直接继承于Object类,没有任何的实现接口,除了构造方法之外全都是抽象方法,HashTable是其子类,需要实现其抽象方法。

三、构造方法

构造方法一、传入两个参数:初始化容量的大小,加载因子

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];初始化时创建Entry类型的table数组
        用于扩容的临界值
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        initHashSeedAsNeeded(initialCapacity);
    }

构造方法二、传入一个参数:初始化容量,加载因子取默认的0.75f

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

构造方法三、无参构造方法,初始化容量为11,加载因子取默认的0.75f

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

构造方法四、传入Map集合类型的对象t

public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

四、核心方法

1、添加元素:put()

 public synchronized V put(K key, V value) {

        if (value == null) {  当value值为null的时候,抛出异常
            throw new NullPointerException();
        }

        Entry tab[] = table;
        int hash = hash(key); 计算key的哈希码
        int index = (hash & 0x7FFFFFFF) % tab.length; 求出key的哈希码所对应的索引下标
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { 
 在tab数组对应索引下标的链表下进行查找遍历,是否存在相同的键值key
            if ((e.hash == hash) && e.key.equals(key)) { 
采用“==”计算哈希码是否相同;采用“.equals()”计算键值key是否相等,需要这两者都满足则说明存在相同的键值key,进行替换操作
                V old = e.value;
                e.value = value;
                return old; 返回旧的value值
            }
        }

        modCount++; 添加元素,tab数组的内部结构发生变化,版本号++
        if (count >= threshold) { 如果元素数量大于阈值
            rehash(); 扩容并进行重哈希计算
            tab = table;
            hash = hash(key);
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        Entry<K,V> e = tab[index]; 创建新的entry
        tab[index] = new Entry<>(hash, key, value, e); 链接到数组中
        count++; 元素数量+1
        return null;
    }
protected void rehash() { 扩容并计算重哈希过程
        int oldCapacity = table.length;   HashTable旧的容量
        Entry<K,V>[] oldMap = table;

        int newCapacity = (oldCapacity << 1) + 1;  新的容量为原来旧的容量的2倍+1
        if (newCapacity - MAX_ARRAY_SIZE > 0) { 
判断新的容量是否超过最大值,如果超过了,将超出的改为最大值
            if (oldCapacity == MAX_ARRAY_SIZE) 如果旧的容量是最大值,则直接返回
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<K,V>[] newMap = new Entry[newCapacity]; 用新的容量创建新的数组

        modCount++; 修改次数+1
        重新计算阈值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        根据新的容量重新初始化hashseed
        boolean rehash = initHashSeedAsNeeded(newCapacity);
   
        table = newMap;
        从后开始遍历旧的元素
        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                if (rehash) {  重新计算哈希码
                    e.hash = hash(e.key);
                }
                int index = (e.hash & 0x7FFFFFFF) % newCapacity; 重新计算位置
                e.next = newMap[index]; 设置元素
                newMap[index] = e; 放入元素
            }
        }
     }

2、获取元素:get()

public synchronized V get(Object key) { 根据键值key获取元素
        Entry tab[] = table;
        int hash = hash(key); 计算键值key的哈希码
        int index = (hash & 0x7FFFFFFF) % tab.length; 计算对应的位置
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { 
从table数组对应的索引下标的链表处开始进行遍历,查找键值key是否存在,与添加元素比较键值key时所使用的思想有出入!!!
            if ((e.hash == hash) && e.key.equals(key)) {
                return e.value;
            }
        }
        return null;
    }

3、删除元素:remove()

public synchronized V remove(Object key) { 根据键值key进行删除
        Entry tab[] = table;
        int hash = hash(key); 计算键值key的哈希码
        int index = (hash & 0x7FFFFFFF) % tab.length; 确定所在的位置
        遍历找到该元素
        for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                modCount++; 修改次数+1
                if (prev != null) { 判断不是第一位的,则将元素进行移动
                    prev.next = e.next;
                } else {
                    tab[index] = e.next;
                }
                count--; 元素数量-1
                V oldValue = e.value;
                e.value = null; 将元素置空,并返回旧的值
                return oldValue;
            }
        }
        return null;
    }

五、总结

1、底层数据结构是数组+双向链表。

2、HashTable的默认初始容量为11,加载因子为0.75。

3、扩容方式是2倍加1的扩容。

4、HashTable所有的方法都是有关键字synchronized修饰线程安全(至于这个关键字后面文章会介绍)。

5、HashTable不管是键值还是值都不可以存储null。

猜你喜欢

转载自blog.csdn.net/qq_40303781/article/details/84256663