HashTable HashTable

HashTable 也是一个 key-value的集合,任何一个非null的对象都可以用来当做一个key或者value。
HashTable 有两个重要的影响因素:initial capacityload factor
当HashTable内的元素个数与容量的占比高于load factor,HashTable就会构造一个容量表,并将原有的内容复制过去。

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

因为HashTable实现的很早,所以它继承的是一个老旧的Dictionary类,Dictionary类已经过时了。

HashTable定义的变量:

	//内部使用了Entry对象作为元素,HashMap使用实现了Entry接口的Node类。
   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;

上面这些变量经常在容器类里面出现。

HashTable的构造函数:

    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);

        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        //同样是使用数组来保存
        table = new Entry<?,?>[initialCapacity];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

HashTable的所有方法都加上了修饰符 synchronized,这表示HashTable的所有方法都是线程安全的。

PUT方法:

1.通过hash找数组中对应的下标指向的链表,如果链表中有相同的key,替换,并返回原来的值。
2.如果找不到就新增加一个Entry,并插入index所在的位置。此时如果表中的元素个数大于阈值,那么就进行rehash。

    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;
        //获取hash值
        int hash = key.hashCode();
        //获取hash值在表中的位置
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        //在对应的entry链表中进行查找,如果找到进行替换,并返回旧的值
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
		//如果找不到,那么就插入
        addEntry(hash, key, value, index);
        return null;
    }

在插入的时候,如果当前的个数大于了阈值,就进行rehash,并创造一个新的Entry来保存元素。

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

        Entry<?,?> tab[] = table;
        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.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

GET方法:

理解PUT方法,再理解GET方法就很简单了。

    public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        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;
    }

REHASH方法:

1.计算新的容量,如果大于MAX_ARRAY_SIZE,就使用MAX_ARRAY_SIZE作为新的容量。
2.生成新的数组
3.对原来数组的每个内容重新进行hash分配。

    protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        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++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;

        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;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

HASHTABLE 和HASHMAP的区别:

  • hashtable是线程安全的,hashmap不是线程安全的。
  • hashmap允许null作为key和value,hashtable不行。