环境和版本

  • Mac M1
  • IDEA 2021
  • Zulu Open JDK

    简介

  • Hashtable 是线程安全的,其和HashMap一致,都是属于Key-Value的数据结构。

  • 但是现在基本不使用Hashtable,因为其锁的粒度太大,影响性能。
  • 所以在这里会讲解其与HashMap的不同点

    数据结构

  • Entry数组

  • 其底层数据结构为数组+链表 ```java // hash表 private transient Entry<?,?>[] table;

private static class Entry implements Map.Entry { final int hash; final K key; V value; Entry next;

  1. protected Entry(int hash, K key, V value, Entry<K,V> next) {
  2. this.hash = hash;
  3. this.key = key;
  4. this.value = value;
  5. this.next = next;
  6. }
  7. @SuppressWarnings("unchecked")
  8. protected Object clone() {
  9. return new Entry<>(hash, key, value,
  10. (next==null ? null : (Entry<K,V>) next.clone()));
  11. }
  12. // Map.Entry Ops
  13. public K getKey() {
  14. return key;
  15. }
  16. public V getValue() {
  17. return value;
  18. }
  19. public V setValue(V value) {
  20. if (value == null)
  21. throw new NullPointerException();
  22. V oldValue = this.value;
  23. this.value = value;
  24. return oldValue;
  25. }
  26. public boolean equals(Object o) {
  27. if (!(o instanceof Map.Entry))
  28. return false;
  29. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  30. return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
  31. (value==null ? e.getValue()==null : value.equals(e.getValue()));
  32. }
  33. public int hashCode() {
  34. return hash ^ Objects.hashCode(value);
  35. }
  36. public String toString() {
  37. return key.toString()+"="+value.toString();
  38. }

}

<a name="NmiLx"></a>
# 构造函数
```java
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);
}

public Hashtable(int initialCapacity) {
    this(initialCapacity, 0.75f);
}

public Hashtable() {
    // 默认大小是11
    this(11, 0.75f);
}

public Hashtable(Map<? extends K, ? extends V> t) {
    // 扩容是2倍
    this(Math.max(2*t.size(), 11), 0.75f);
    putAll(t);
}

put方法

// 线程安全的方法
public synchronized V put(K key, V value) {
    // Make sure the value is not null
    // 值不能为null
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    // 此处计算key的hashCode
    // 所以key也不能为null 否则会抛出空指针
    // 总结:key和value都不能为null
    // 但是HashMap的key和value都可以为null,因为HashMap的key为null,其hashcode为0
    int hash = key.hashCode();
    // hash & 0x7FFFFFFF == hash & Integer.MAX_VALUE
    int index = (hash & 0x7FFFFFFF) % tab.length;
    // 计算出index
    @SuppressWarnings("unchecked")
    // 取出index下标的entry元素
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    // 遍历 entry
    for(; entry != null ; entry = entry.next) {
        // 当hash == hash 并且 key entry.key.equals(key)
        // 就进行替换
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }

    // 没找到就加入到数组中
    addEntry(hash, key, value, index);
    return null;
}

addEntry方法

// 添加Entry数组
// hash:hash值
// key:key
// value:value
// index:哈希表下标
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 引用到扩容之后哈希表
        tab = table;
        // 计算hashcode
        hash = key.hashCode();
        // 计算桶坐标
        index = (hash & 0x7FFFFFFF) % tab.length;
    }

    // Creates the new entry.
    @SuppressWarnings("unchecked")
    // 得到index下标的引用
    Entry<K,V> e = (Entry<K,V>) tab[index];
    // tab index 添加Entry
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

rehash方法

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 扩容方法
protected void rehash() {
    // 旧的数组容量
    int oldCapacity = table.length;
    // 旧的map
    Entry<?,?>[] oldMap = table;

    // overflow-conscious code
    // 新的容量为 2倍 + 1
    int newCapacity = (oldCapacity << 1) + 1;
    // 如果 newCapacity - Integer.MAX_VALUE - 8 > 0
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        // 如果 oldCapacity == MAX_ARRAY_SIZE
        if (oldCapacity == MAX_ARRAY_SIZE)
            // Keep running with MAX_ARRAY_SIZE buckets
            return;
        // 新的为 MAX_ARRAY_SIZE
        newCapacity = MAX_ARRAY_SIZE;
    }
    // 创建新大小newMap 
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

    // 修改次数
    modCount++;
    // 计算扩容阈值
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    table = newMap;

    // 遍历向新的map进行赋值
    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;
    // hash
    int hash = key.hashCode();
    // 计算index
    int index = (hash & 0x7FFFFFFF) % tab.length;
    // 遍历
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        // hash && equals 则返回
        if ((e.hash == hash) && e.key.equals(key)) {
            return (V)e.value;
        }
    }
    return null;
}

remove方法

public synchronized V remove(Object key) {
    Entry<?,?> tab[] = table;
   // hash
    int hash = key.hashCode();
    // 计算index
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> e = (Entry<K,V>)tab[index];
    // 遍历
    for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
        // hash & equals
        if ((e.hash == hash) && e.key.equals(key)) {
            modCount++;
            // 前驱不为 null 
            // 后移一个
            if (prev != null) {
                prev.next = e.next;
            } else {
                // 否则直接后移
                tab[index] = e.next;
            }
            count--;
            V oldValue = e.value;
            e.value = null;
            return oldValue;
        }
    }
    return null;
}

contains方法

// 是否包含value
public synchronized boolean contains(Object value) {
    if (value == null) {
        throw new NullPointerException();
    }

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

containsKey方法

public synchronized boolean containsKey(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 true;
        }
    }
    return false;
}

containsValue方法

public boolean containsValue(Object value) {
    return contains(value);
}

总结

  • 线程安全:Hashtable是线程安全的,HashMap是线程不安全的
  • 默认大小和扩容:Hashtable默认大小是11,默认扩容因子是0.75,一次扩容大小是2倍+1;HashMap默认大小是16,默认扩容因子是0.75,一次扩容是2倍
  • 底层数据结构:Hashtable底层是Entry数组,也就是数组+拉链法;HashMap底层是数组+链表+红黑树
  • Key和Value的限制:Hashtable的key和value都不可为null;HashMap的key可以为null,但是只有一个,且为null的hashcode =0,值可以为null
  • contains方法:Hashtable还有具有迷惑性的contains方法;HashMap去除掉了
  • 继承的父类不同: Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口
  • 遍历的实现方式不同:Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式
  • Hash计算方式:Hashtable为取key的hash;而HashMap重新计算,取高低16位计算。