HashTable

和HashMap相似,不过HashTable是线程安全的

与HashMap的区别

  • HashTable线程安全
  • HashMap允许key和value为空,HashTable不允许保存null
  • HashMap的迭代器是fail-fast(不允许迭代的过程中出现数据的修改),HashTable的迭代器是enumerator,允许迭代器本身的remove方法(同样不支持别的线程进行修改操作)

基础源码

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {
    private transient Entry<?,?>[] table;  //用于存储数据的数组桶
    private transient int count;    //entry数量
    private int threshold;  //扩容阈值
    private float loadFactor;   //填充因子
    private transient int modCount = 0;
}

相比HashMap来说HashTable就没有再定义那么多的默认值了(默认长度之类的)

构造函数

public Hashtable() {
    this(11, 0.75f);
}
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);
}
  • 如果不指定长度的话,默认的数组长度是11
  • 这里的table数组会在构造函数中就初始化
  • 这里的threshold会真正的取数组长度*填充因子

put方法

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];
    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;
}

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++;
}
  • 这里的put方法用synchronized进行了修饰以确保线程安全
  • 对数据进行数组桶位映射使用的方法是(hash & 0x7FFFFFFF) % tab.length和HashMap不同,这里的hash是直接算出来的hash值,而HashMap的hash还进过了特殊处理,这里hash & 0x7FFFFFFF是如果hash为负转为正数,然后对table长度取模, 两种方式的特点,简单的说位运算销量比数学运算效率高,HashMap的优化度更高
  • HashTable看起来没有使用红黑树而只有链表的方式
  • 如果key已存在则进行更新,如果key不存在则插入新的数据(entry),且每次插入一个entry的时候count累加1,当count数量大于阈值的时候进行刷新(扩容)

扩容

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

这个很简单,判断一下扩容后的大小不能超过最大长度(具体控制条件看一下上面代码就行),扩展数组长度后将数据重新通过映射算法进行分发(数组长度边了,映射到的位置也就发生了变化)

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

get方法就很简单了,直接进行遍历