PerKins Zhu

Le vent se lève,il faut tenter de vivre.

导航

源码解读—HashTable

Posted on 2016-08-21 12:40  PerKins.Zhu  阅读(1383)  评论(0编辑  收藏  举报

在上一篇学习过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--------------------------------------------------