zoukankan      html  css  js  c++  java
  • 源码解读—HashTable

    在上一篇学习过HashMap(源码解读—HashMap)之后对hashTable也产生了兴趣,随即便把hashTable的源码看了一下。和hashMap类似,但是也有不同之处。

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

    实现接口:Map,Cloneable,Serializable 

    继承自Dictionary,此抽象类同AbstractMap,

    1、四个构造方法:

    第一个构造方法: public Hashtable() ;

        /**
         * 用默认容量(11)和加载系数(0.75)构造一个空的hashTable
         * Constructs a new, empty hashtable with a default initial capacity (11)
         * and load factor (0.75).
         */
        public Hashtable() {
            //此处竟然没有定义成常量!!!看来oracle的开发人员也是人!!!
            this(11, 0.75f);
        }

    第二个构造方法:  public Hashtable(int initialCapacity) 

        /**
         * 用指定的容量和默认的系数构造一个新的hashTable
         * 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);
        }

    第三个构造方法: public Hashtable(int initialCapacity, float loadFactor);

        /**
         * 用指定的容量和加载系数构造一个空的hashTable
         * 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;
            //数组+单向链表作为hashTable的数据存储容器。初始化数组
            table = new Entry[initialCapacity];
            //计算扩容阀值,当count(hashTable中的所有entry,不是table数组的size) >= threshold的时候进行扩容操作。
            //扩容阀值=容量*加载系数  扩容法制最大不超过(MAX_ARRAY_SIZE + 1)
            threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
            //初始化hashSeed,在进行hashCode计算的时候需要使用
            initHashSeedAsNeeded(initialCapacity);
        }

    第四个构造方法:public Hashtable(Map<? extends K, ? extends V> t)

        /**
         * 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
         */
        public Hashtable(Map<? extends K, ? extends V> t) {
            //TODO 创建一个新的hashTable,这里可以肯出hashTable的加载系数一直都是0.75,不允许调用处进行设置
            //当t的count>11的时候程序取2*t.size(),为甚么取2倍?  标示:XX01
            this(Math.max(2*t.size(), 11), 0.75f);
            //把数据转存到hashTable中
            putAll(t);
        }

    在这里调用  public synchronized void putAll(Map<? extends K, ? extends V> t) ;把所有数据放进hashTable中

        /**
         * 把所有的 映射从指定的map复制到hashTable中
         * 如果给定的map中的key值已经存在于hashTable中,则将会覆盖hashTable中key所对应的value(hashTable中key值不允许重复)
         * Copies all of the mappings from the specified map to this hashtable.
         * These mappings will replace any mappings that this hashtable had for any
         * of the keys currently in the specified map.
         *
         * @param t mappings to be stored in this map
         * @throws NullPointerException if the specified map is null
         * @since 1.2
         */
        public synchronized void putAll(Map<? extends K, ? extends V> t) {
            //foreach 循环map数据put到hashTable中
            for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
                put(e.getKey(), e.getValue());
        }

    循环调用  public synchronized V put(K key, V value) ;进行entry复制

       /**
         * 把给定的key和value进行映射后放入hashTable,
         * key和value值都不允许为null(hashMap是允许key和value为null的)
         * 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>
         *
         *通过调用get(K key)方法可以取出value值
         * 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)
         */
        //synchronized:看到这个关键字没?这就是hashTable线程安全的原因,加锁了,不允许两个线程同时操作此方法
        public synchronized V put(K key, V value) {
            // Make sure the value is not null 不允许value为null,否则抛出 空指针异常
            if (value == null) {
                throw new NullPointerException();
            }
    
            // Makes sure the key is not already in the hashtable.
            Entry tab[] = table;
            //取hashCode
            int hash = hash(key);
            //计算table下标(hashTable的数据存储容器为数组+链表(链表是为了解决hash冲突))
            //至于为什么这样计算?我也不知道,智商是硬伤啊!
            int index = (hash & 0x7FFFFFFF) % tab.length;
            //从table取出entrust(链表头结点,如果有的话),然后循环链表找到key,如果找到则进行value覆盖做操
            for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
                if ((e.hash == hash) && e.key.equals(key)) {
                    V old = e.value;
                    e.value = value;
                    return old;
                }
            }
            //如果hashTable没有该映射则新增
            //操作次数++
            modCount++;
            //判断是否需要进行扩容,注意:threshold不是table的长度,而是capacity*loadFactor
            if (count >= threshold) {
                // Rehash the table if the threshold is exceeded  如果阀值过大则进行扩容
                rehash();
                tab = table;
                hash = hash(key);
                index = (hash & 0x7FFFFFFF) % tab.length;
            }
    
            // Creates the new entry.   创建一个新的节点,把他放入table
            Entry<K,V> e = tab[index];
            tab[index] = new Entry<>(hash, key, value, e);
            //数据量++
            count++;
            return null;
        }

    2、其他重点方法

    keySet和valueSet的迭代器使用

        /**
         * 返回key的迭代器/枚举器?该怎么叫呢?
         * Returns an enumeration of the keys in this hashtable.
         *
         * @return  an enumeration of the keys in this hashtable.
         * @see     Enumeration
         * @see     #elements()
         * @see     #keySet()
         * @see     Map
         */
        public synchronized Enumeration<K> keys() {
            return this.<K>getEnumeration(KEYS);
        }
    
        /**
         * 返回value的枚举器,通过这个没去器可以连续取出value的值
         * Returns an enumeration of the values in this hashtable.
         * Use the Enumeration methods on the returned object to fetch the elements
         * sequentially.
         *
         * @return  an enumeration of the values in this hashtable.
         * @see     java.util.Enumeration
         * @see     #keys()
         * @see     #values()
         * @see     Map
         */
        public synchronized Enumeration<V> elements() {
            return this.<V>getEnumeration(VALUES);
        }
    
        //根据type(KEY/VALUE)取出对应的枚举器
        private <T> Enumeration<T> getEnumeration(int type) {
            //如果hashTable数据为空则返回空的枚举器
            if (count == 0) {
                return Collections.emptyEnumeration();
            } else {
                return new Enumerator<>(type, false);
            }
        }
    
        //获取迭代器,没什么可分析的,如果不了解可以找一下迭代器的使用
        private <T> Iterator<T> getIterator(int type) {
            if (count == 0) {
                return Collections.emptyIterator();
            } else {
                return new Enumerator<>(type, true);
            }
        }

    根据key取出value: public synchronized V get(Object key);

        /**
         * 取出给定的key所映射的value,如果hashTable中没有此key则返回null
         * Returns the value to which the specified key is mapped,
         * or {@code null} if this map contains no mapping for the key.
         * 
         *下面又啰嗦了一遍,这里的key相等时根据key.equals(k)来进行判断的,也就是字符串内容相等就OK
         * <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)
         */
        public synchronized V get(Object key) {
            //还是套路:计算index,从table中取出entry节点,再循环链表找key.equals(key).
            Entry tab[] = table;
            int hash = hash(key);
            int index = (hash & 0x7FFFFFFF) % tab.length;
            for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
                //判断相等的时候需要key的hashCode和内容同时相同才可
                if ((e.hash == hash) && e.key.equals(key)) {
                    return e.value;
                }
            }
            return null;
        }

    扩容操作:当count(entry节点的数量/Key的数量) >= threshold 时进行扩容

       /**
         * 增加容量并且对hashTable进行内部重整,以便于有效的接收容纳更多的entry。
         * 当key的数量大于hashTable的阀值时会自动调用这个方法。
         * 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.
         */
        protected void rehash() {
            //套路:先保存老数据
            int oldCapacity = table.length;
            Entry<K,V>[] oldMap = table;
    
            // overflow-conscious code
            //计算出新的容量:(oldCapacity*2)+1
            int newCapacity = (oldCapacity << 1) + 1;
            //如果扩容之后的容量大于容量的最大值则进行判断
            if (newCapacity - MAX_ARRAY_SIZE > 0) {
                //如果容量已经是最大值了则无法集训进行扩容,只能return了。终止操作
                if (oldCapacity == MAX_ARRAY_SIZE)
                    // Keep running with MAX_ARRAY_SIZE buckets
                    return;
                //否则取最大值
                newCapacity = MAX_ARRAY_SIZE;
            }
            
            Entry<K,V>[] newMap = new Entry[newCapacity];
            //操作此时++
            modCount++;
            //重新计算 阀值
            threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
            //TODO  标示:XX03
            boolean rehash = initHashSeedAsNeeded(newCapacity);
    
            table = newMap;
            //循环table
            for (int i = oldCapacity ; i-- > 0 ;) {
                //循环链表
                for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
                    //注意这里,这里传递的知识引用,也就是说在堆空间中并没有复制一个新的entry,
                    //只是把原来的entry的引用复制了一份(不了解的可以查找一下java 的堆栈内存数据存储)
                    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];
                    //把老数据放到新的table中
                    newMap[index] = e;
                }
            }
        }

    hashTable继承了序列化和克隆接口,在这里没有对序列化读写方法和克隆方法进行分析,这些都是固定的格式,感兴趣的朋友可以看一些这些接口的使用方法。

    在这里还涉及到keySet valueSet 和entrySet,这主要是用来进行迭代使用的,对迭代器不了解的可以找一些迭代器使用的相关资料,这些都是套路,不再分析。

    所有的数据操作都离不开增删改查,而这些不同方法很多步骤都是相同的,先进行数据验证,然后进行数据查找,找到之后进行删、改操作。抓住核心其实就没什么难理解的了。

    -------------------------------------over-------------------------------------------------- 

  • 相关阅读:
    [BZOJ4029][HEOI2015]定价
    [BZOJ3261]最大异或和
    [BZOJ3166][Heoi2013]Alo
    [BZOJ1030][JSOI2007]文本生成器
    [BZOJ2595][Wc2008]游览计划
    Speculative store buffer
    十四 oracle 视图
    十三oracle --控制结构(分支,循环,控制)
    十二、oracle 数据库(表)的逻辑备份与恢复
    十一、oracle 数据库管理员
  • 原文地址:https://www.cnblogs.com/PerkinsZhu/p/5792515.html
Copyright © 2011-2022 走看看