美文网首页
Hashtable源码研究

Hashtable源码研究

作者: 涂豪_OP | 来源:发表于2018-08-03 15:45 被阅读6次

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

    //跟HashMap相比,继承的类不一样,实现具
    //的接口倒是一样的,说明他们具有类似的功能
    public class Hashtable<K,V>
        extends Dictionary<K,V>
        implements Map<K,V>, 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<? extends K, ? extends V> t) {
            this(Math.max(2*t.size(), 11), 0.75f);
            putAll(t);
        }
    }
    

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

        /**
         * Maps the specified <code>key</code> to the specified
         * <code>value</code> in this hashtable. Neither the key nor the
         * value can be <code>null</code>. <p>
         *
         * The value can be retrieved by calling the <code>get</code> 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 <code>null</code> if it did not have one
         * @exception  NullPointerException  if the key or value is
         *               <code>null</code>
         * @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<K,V> entry = (Entry<K,V>)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<K,V> e = (Entry<K,V>) 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<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
    
                    //将old赋值给e,e代表遍历到的节点
                    Entry<K,V> e = old;
    
                    //old指向他的后继节点,下次遍历就遍历到后继节点了
                    old = old.next;
    
                    //重新计算Entery的索引,因为newCapacity变了,所以index也会变
                    int index = (e.hash & 0x7FFFFFFF) % newCapacity;
    
                    //把新桶上已存在链表节点作为当前节点的后继节点
                    e.next = (Entry<K,V>)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.
         *
         * <p>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 <code>null</code> if the key did not have a mapping
         * @throws  NullPointerException  if the key is <code>null</code>
         */
        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<K,V> e = (Entry<K,V>)tab[index];
            //拿到桶的首节点后,往死里遍历首节点后面的链表
            for(Entry<K,V> 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源码研究

          本文链接:https://www.haomeiwen.com/subject/ovmyvftx.html