Hashtable源码研究

    上几篇笔记研究了HashMap和LinkedHashMap,此笔记研究Hashtable。
一:概述
    先来看下Hashtable的类信息

//跟HashMap相比,继承的类不一样,实现具
//的接口倒是一样的,说明他们具有类似的功能
public class Hashtable
    extends Dictionary
    implements Map, Cloneable, java.io.Serializable {
    /**
     * The hash table data.
     * hash数组,跟HashMap一样
     */
    private transient Entry[] table;

    /**
     * The total number of entries in the hash table.
     * 元素个数
     */
    private transient int count;

    /**
     * The table is rehashed when its size exceeds this threshold.  (The
     * value of this field is (int)(capacity * loadFactor).)
     * 扩容阈值
     *
     * @serial
     */
    private int threshold;

    /**
     * The load factor for the hashtable.
     * 加载因子
     *
     * @serial
     */
    private float loadFactor;

    /**
     * The number of times this Hashtable has been structurally modified
     * Structural modifications are those that change the number of entries in
     * the Hashtable or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the Hashtable fail-fast.  (See ConcurrentModificationException).
     * 修改次数
     */
    private transient int modCount = 0;

    /**
     * Constructs a new, empty hashtable with the specified initial
     * capacity and the specified load factor.
     *
     * @param      initialCapacity   the initial capacity of the hashtable.
     * @param      loadFactor        the load factor of the hashtable.
     * @exception  IllegalArgumentException  if the initial capacity is less
     *             than zero, or if the load factor is nonpositive.
     * 两个参数的构造函数
     */
    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);

        //如果初始容量是0的话,那么置成1
        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;

        //创建指定长度的Entry数组
        table = new Entry[initialCapacity];

        //计算阈值
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

    /**
     * Constructs a new, empty hashtable with the specified initial capacity
     * and default load factor (0.75).
     *
     * @param     initialCapacity   the initial capacity of the hashtable.
     * @exception IllegalArgumentException if the initial capacity is less
     *              than zero.
     *
     * 带有容量的构造函数
     */
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    /**
     * Constructs a new, empty hashtable with a default initial capacity (11)
     * and load factor (0.75).
     * 默认构造函数,容量是11,加载因子是0.75;HashMap的容量是16,加载因子也是0.75
     */
    public Hashtable() {
        this(11, 0.75f);
    }

    /**
     * Constructs a new hashtable with the same mappings as the given
     * Map.  The hashtable is created with an initial capacity sufficient to
     * hold the mappings in the given Map and a default load factor (0.75).
     *
     * @param t the map whose mappings are to be placed in this map.
     * @throws NullPointerException if the specified map is null.
     * @since   1.2
     *
     * 根据一个Map创建一个Hashtable,容量是原来容量的两倍和11的最大值
     */
    public Hashtable(Map t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }
}

    Hashtable的类信息还是比较简单的,下面看下他存入元素的方法put(K key, V value):

    /**
     * Maps the specified key to the specified
     * value in this hashtable. Neither the key nor the
     * value can be null. 

* * The value can be retrieved by calling the get method * with a key that is equal to the original key. * * @param key the hashtable key * @param value the value * @return the previous value of the specified key in this hashtable, * or null if it did not have one * @exception NullPointerException if the key or value is * null * @see Object#equals(Object) * @see #get(Object) */ public synchronized V put(K key, V value) { // Make sure the value is not null //如果value为空就死给你看 if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. //拿到Hashtable的数组 Entry tab[] = table; //计算key的hash值 int hash = key.hashCode(); //桶的索引是用key的hash值和0x7FFFFFFF做与运算,最后对数组长度取余 int index = (hash & 0x7FFFFFFF) % tab.length; //拿到指定桶上的Entry对象 @SuppressWarnings("unchecked") Entry entry = (Entry)tab[index]; //Hashtable上只有链表,没有红黑树,所以遍历链表 for(; entry != null ; entry = entry.next) { //如果遍历到的Entry的hash和目标hash一样,而且两个的key //也相等,那么只需要更新value即可,同时将老的value返回回去 if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } //若干Hashtable里面没有key与目标key一样的Entry,那么新插入一个 addEntry(hash, key, value, index); //如果是插入全新的Entry的话,就返回空 return null; }

    put方法就是先查找,找到了就更新;没找到就插入一个全新的Entry,首先可以看到,put这个方法加了synchronized,说明这个方法是线程安全的,实际上Hashtable的大部分方法都是线程安全的,这是跟HashMap的一个重要区别;然后在put里面对value进行了空判断,也就是说,HashTable是不允许存入空的键值对的,这也是和HashMap的一个在重要区别;下面分析插入的方法addEntry:

private void addEntry(int hash, K key, V value, int index) {
    //modCount自增,注意到没,更新操作是不自增的
    modCount++;

    Entry tab[] = table;

    //如果当前元素数量超出阈值
    if (count >= threshold) {
        // Rehash the table if the threshold is exceeded
        //重新hash,也就是扩容
        rehash();

        tab = table;

        //计算key的hash值
        hash = key.hashCode();

        //计算key的索引
        index = (hash & 0x7FFFFFFF) % tab.length;
    }

    // Creates the new entry.
    //将桶上原来的Entry赋值给e
    @SuppressWarnings("unchecked")
    Entry e = (Entry) tab[index];

    //根据原来的Entry、hash、key和value
    //创建一个新的Entry放到指定的桶上
    tab[index] = new Entry<>(hash, key, value, e);

    //数量自增
    count++;
}

     addEntry方法的核心是rehash(),这个方法看起来是重新计算hash,事实上他确实也计算了,这个方法的核心作用还是扩容:

    /**
     * Increases the capacity of and internally reorganizes this
     * hashtable, in order to accommodate and access its entries more
     * efficiently.  This method is called automatically when the
     * number of keys in the hashtable exceeds this hashtable's capacity
     * and load factor.
     */
    @SuppressWarnings("unchecked")
    protected void rehash() {
        //老数组的长度
        int oldCapacity = table.length;

        //将老数组赋值给oldMap
        Entry[] oldMap = table;

        // overflow-conscious code
        //新数组的长度是老数组长度的2n + 1
        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++;

        //计算新的阈值,在新容量*加载因子和最大值 + 1之间取最小值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);

        //将新数组赋值给table
        table = newMap;

        //双重遍历老数组,第一重遍历是遍历桶上的各个Entry,将老数组的节点移到新数组
        for (int i = oldCapacity ; i-- > 0 ;) {
            //第二重遍历是遍历桶上Entry后面接的链表
            for (Entry old = (Entry)oldMap[i] ; old != null ; ) {

                //将old赋值给e,e代表遍历到的节点
                Entry e = old;

                //old指向他的后继节点,下次遍历就遍历到后继节点了
                old = old.next;

                //重新计算Entery的索引,因为newCapacity变了,所以index也会变
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;

                //把新桶上已存在链表节点作为当前节点的后继节点
                e.next = (Entry)newMap[index];

                //遍历到的节点放到新数组的桶上,发现没?这是链表的头结点
                newMap[index] = e;
            }
        }
    }

    理解了HashMap的扩容后,再理解Hashtable的扩容就简单多了;需要注意的是,老数组上的链表,经过扩容后可能会被拆散;那些没被拆散的节点,也就是重新落入同一个桶的节点,他们的前驱和后继的关系反过来了,原来是前驱节点的,在新数组里面就是后继节点了,反之亦然。既然有put方法,那就必然有get方法:

    /**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     * 

More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key.equals(k))}, * then this method returns {@code v}; otherwise it returns * {@code null}. (There can be at most one such mapping.) * * @param key the key whose associated value is to be returned * @return the value to which the specified key is mapped, or * {@code null} if this map contains no mapping for the key * @throws NullPointerException if the specified key is null * @see #put(Object, Object) */ @SuppressWarnings("unchecked") public synchronized V get(Object key) { Entry tab[] = table; //计算key的hash int hash = key.hashCode(); //根据hash值计算桶的索引 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; }

    get方法超简单,不多说。增、改、查都分析了,下面分析删的动作:

    /**
     * Removes the key (and its corresponding value) from this
     * hashtable. This method does nothing if the key is not in the hashtable.
     *
     * @param   key   the key that needs to be removed
     * @return  the value to which the key had been mapped in this hashtable,
     *          or null if the key did not have a mapping
     * @throws  NullPointerException  if the key is null
     */
    public synchronized V remove(Object key) {
        Entry tab[] = table;
        //计算key的hash
        int hash = key.hashCode();

        //根据hash计算桶的索引index
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry e = (Entry)tab[index];
        //拿到桶的首节点后,往死里遍历首节点后面的链表
        for(Entry prev = null ; e != null ; prev = e, e = e.next) {
            //如果key的hash相等,而且key本身也相等,说明就是要删掉这个节点
            if ((e.hash == hash) && e.key.equals(key)) {
                //自增
                modCount++;

                //如果prev不为空,说明要删除的不是头结点
                if (prev != null) {
                    //那么将目标节点的头结点指向目标节点的后继节点
                    prev.next = e.next;
                } else {
                    //如果删除的就是头结点,那么将原来头结点的后继节点指定为头结点
                    tab[index] = e.next;
                }

                //元素总数 - 1
                count--;

                //取出要删除的节点的value
                V oldValue = e.value;

                //将e的value置空(需要吗?)
                e.value = null;

                //返回删除节点的value
                return oldValue;
            }
        }
        return null;
    }

    可以看到,remove方法也是非常简单的。
    Hashtable就讲这么多吧,其他的方法都差不多,基本上能都是通过hash计算桶的索引,然后操作链表,非常简单;我个人觉得Hashtable是HashMap的简化版,就是把HashMap中的红黑树去掉,同时在公开的方法增加了同步操作;只要理解了HashMap,理解Hashtable就水到渠成了,不管是理解哪个,都要理解链表和树。

你可能感兴趣的:(Hashtable源码研究)