HashTable源码学习

一.介绍

1.HashMap和HashTable的区别

HashTable源码学习

1.相同点

  • 二者都实现了Map接口。
  • 底层都是哈西表

2.不同点

  • Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。

  • Hashtable 第一次创建对象的时候就会给底层数组开辟空间 Entry[] 11

    HashMap 创建对象时 没有给底层数组进行空间开辟

  • HashMap把Hashtable的contains方法去掉了。改成containsValue和containsKey

    Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。

  • Hashtable中,key和value都不允许出现null值

    HashMap中,null可以作为键,这样的键只有一个

  • 哈希值的使用不同,HashTable直接使用对象的hashCode

    HashMap重新计算hash值。

  • HashTable在不指定容量的情况下的默认容量为11

    HashMap为16

  • Hashtable不要求底层数组的容量一定要为2的整数次幂

    HashMap则要求一定为2的整数次幂。

  • Hashtable扩容时,将容量变为原来的2倍加1

    HashMap扩容时,将容量变为原来的2倍。

  • Hashtable 中的方法是Synchronize的。

    HashMap线程不安全

  • 计算index的方法不同:

    • HashTable: index = (hash & 0x7FFFFFFF) % tab.length
    • HashMap:index = hash & (tab.length – 1)

2.HashTable

像HashMap一样,Hashtable在哈希表中存储键/值对。当使用一个哈希表,要指定用作键的对象,以及要链接到该键的值。

然后,该键经过哈希处理,所得到的散列码被用作存储在该表中值的索引

二.源码部分

1.基本属性

继承Dictionary 类

是一个抽象类,用来存储键/值对,作用和Map类相似。

给出键和值,你就可以将值存储在Dictionary对象中。一旦该值被存储,就可以通过它的键来获取它。所以和Map一样, Dictionary 也可以作为一个键/值对列表。

Cloneable是标记型的接口,它们内部都没有方法和属性,实现 Cloneable来表示该对象能被克隆

java.io.Serializable接口:

可以启用其序列化功能。未实现次接口的类无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。

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

    /**
     * The hash table data.哈希表数据
     */
    private transient Entry<?,?>[] table;

    /**
     * The total number of entries in the hash table.哈希表中enyty的总数
     */
    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;

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = 1421746759512286392L;
    
        /**
     * The maximum size of array to allocate.
     * 要分配的数组的最大大小。
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

2.构造函数

/**
 * 无参构造,默认容量为11,加载因子为0.75
 * Constructs a new, empty hashtable with a default initial capacity (11)
 * and load factor (0.75).
 */
public Hashtable() {
    this(11, 0.75f);
}
/**
 * Constructs a new, empty hashtable with the specified initial capacity
 * and default load factor (0.75).
 * 创建指定大小的哈希表
 * 初始容量用户自定义,加载因子为默认的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 the specified initial
 * capacity and the specified load factor.
 * 创建了一个指定大小的哈希表,并且通过fillRatio指定填充比例
 * @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;
    //创建一个新的大小为初始容量的enytry数组
    table = new Entry<?,?>[initialCapacity];
    //阈值为initialCapacity * loadFactor或最大值
    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
/**
 * 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).
 *  第四个构造方法创建了一个以M中元素为初始化元素的哈希表。
 * 哈希表的容量被设置为M的两倍,或是11
 * @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) {
    this(Math.max(2*t.size(), 11), 0.75f);
    putAll(t);
}

3.contains()

/**
 * Tests if some key maps into the specified value in this hashtable.
 * This operation is more expensive than the {@link #containsKey
 * containsKey} method.
 * 测试此映射表中是否存在与指定值关联的键。
 * <p>Note that this method is identical in functionality to
 * {@link #containsValue containsValue}, (which is part of the
 * {@link Map} interface in the collections framework).
 *
 * @param      value   a value to search for
 * @return     <code>true</code> if and only if some key maps to the
 *             <code>value</code> argument in this hashtable as
 *             determined by the <tt>equals</tt> method;
 *             <code>false</code> otherwise.
 * @exception  NullPointerException  if the value is <code>null</code>
 */
public synchronized boolean contains(Object value) {
    //如果传入的value值为空,则抛出异常
    if (value == null) {
        throw new NullPointerException();
    }
    //2层循环遍历数组及其链表
    Entry<?,?> tab[] = table;
    for (int i = tab.length ; i-- > 0 ;) {
        for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
            if (e.value.equals(value)) {
                return true;
            }
        }
    }
    return false;
}

4.put()

/**
 * 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.
 * 将指定 key 映射到此哈希表中的指定 value。
 * @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确保该值不为空
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    //得到哈希值
    int hash = key.hashCode();
    //得到数组下标
    int index = (hash & 0x7FFFFFFF) % tab.length;
    //忽略警告
    @SuppressWarnings("unchecked")
            //将对应的数组元素保留起来
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    //当该位置已有元素,则发生哈希冲突,遍历链表找通过equal方法找到key
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            //覆盖原来的值
            V old = entry.value;
            entry.value = value;
            //返回原来的值
            return old;
        }
    }

5.addEntry()

private void addEntry(int hash, K key, V value, int index) {
    modCount++;

    Entry<?,?> tab[] = table;
    //如果数组有效值等于阈值,将进行扩容操作,然后在计算新的index
    if (count >= threshold) {
        // Rehash the table if the threshold is exceeded 如果超过阈值,请重新散列表
        rehash();

        tab = table;
        hash = key.hashCode();
        index = (hash & 0x7FFFFFFF) % tab.length;
    }

    // Creates the new entry.创建新条目。count++
    @SuppressWarnings("unchecked")
    Entry<K,V> e = (Entry<K,V>) tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

6.扩容机制

/**
 * Increases the capacity of and internally reorganizes this
 * hashtable,增加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;
    Entry<?,?>[] oldMap = table;

    // overflow-conscious code
    //新的容量为原来的一半 + 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<?,?>[] newMap = new Entry<?,?>[newCapacity];

    modCount++;
    //阈值也重新赋值,如若已经超出最大值,则为MAX_ARRAY_SIZE + 1
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    //将table指向新数组
    table = newMap;
    //遍历原来的hashtable,将其拷贝过来
    for (int i = oldCapacity ; i-- > 0 ;) {
        for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
            Entry<K,V> e = old;
            old = old.next;
            //数组下标值不同于hashmap,都重新计算
            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            e.next = (Entry<K,V>)newMap[index];
            newMap[index] = e;
        }
    }
}

三.总结

  • Hash:是一种信息摘要算法,它还叫做哈希,或者散列。我们平时使用的MD5,SHA1都属于Hash算法,通过输入key进行Hash计算,就可以获取key的HashCode(),比如我们通过校验MD5来验证文件的完整性。
  • Hashtable 实现并发安全的原理是通过 synchronized 关键字
  • 当线程数量增加的时候,Hashtable 的性能会急剧下降,因为每一次修改都需要锁住整个对象,而其他线程在此期间是不能操作的。不仅如此,还会带来额外的上下文切换等开销,所以此时它的吞吐量甚至还不如单线程的情况。
上一篇:快速体验 Sentinel 集群限流功能,只需简单几步


下一篇:Missing space before value for key ‘routes‘