HashMap与Hashtable的八点区别(源码解析)

区别

  1. 继承的父类不同
  2. 初始化数组长度,和扩容时的增量不同
  3. 是否允许存储空值不同
  4. 获取Hash值和数组下标的方法不同
  5. 底层数据结构不同
  6. 扩容方法不同
  7. 线程安全问题
  8. 性能存在差距

1. 继承的父类不同

  • HashMap 继承了 AbstractMap
  • Hashtable 继承了 Dictionary
/*  * HashMap源码  */ //HashMap继承了AbstractMap public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {} 
/*  * Hashtable源码  */ //Hashtable继承了Dictionary Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {} 

2. 初始化数组长度,和扩容时的增量不同

  • HashMap 默认的初始数组长度是 16,默认的加载因子是 0.75,每次扩容变成之前数组的 2 倍长度。
  • Hashtable 默认的初始数组长度是 11,默认的加载因子是 0.75,每次扩容是之前数组的 2 倍长度加 1。
/*  * HashMap源码  */ //HashMap 中的扩容方法resize(),旧数组长度*2得到新数组长度 final Node<K,V>[] resize() {    ... ...         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&                  oldCap >= DEFAULT_INITIAL_CAPACITY)             newThr = oldThr << 1; // double threshold    ... ... } 
/*  * Hashtable源码  */ //Hashtable中的扩容方法rehash(),旧数组长度左移1位加1得到新数组长度 protected void rehash() {	 	... ...      int newCapacity = (oldCapacity << 1) + 1;     ... ...  } 

3. 是否允许存储空值不同

  • HashMap 允许value为空,只能有一个key为空。
  • 而Hashtable 的 key,value 都不允许为空,在源码中的 put 方法里限制了如果 value 为空则抛出空指针异常,但是并没有限制 key,它的 hash 值就是 key.hashCode( ),对象为空调用 hashCode( ) 方法会抛出空指针异常。
/*  * HashMap源码  */ //HashMap如果key为空,则会放在数组下标为0的Node桶里,根据put方法的流程新传入的key为null的键值 //会覆盖掉之前的,所以最多只会有一个key的为null static final int hash(Object key) {     int h;     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 
/*  * Hashtable源码  */ //Hashtable中hash值就是key的hashCode,对象为空的话hashCoe()方法会报空指针异常,对于value有了约束,为空也会抛出空指针异常 public synchronized V put(K key, V value) {     ... ...     if (value == null) {         throw new NullPointerException();     }     ... ...     int hash = key.hashCode();     ... ... } 

4. 获取Hash值和数组下标的方法不同

  • HashMap 的 hash( ) 方法会把key的 hashCode 与它的高16位异或,目的是使它的低16位更加散列,减少哈希冲突,提高性能。HashMap 获取数组下标通过 (数组长度 - 1) & hash 计算得到,实际上就是 hash %(数组长度)。 - 具体解释
  • Hashtable 的 hash 值由 key.hashCode( ) 得到,数组下标是先把 (hash & 0x7FFFFFFF),再对数组长度取模。
/*  * HashMap源码  */ //HashMap的hash()方法会把key的hashCode与它的高16位异或 static final int hash(Object key) {     int h;     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }  //HashMap获取数组下标通过 (数组长度 - 1) & hash 计算得到 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                boolean evict) {     ... ...     if ((p = tab[i = (n - 1) & hash]) == null)         tab[i] = newNode(hash, key, value, null);     ... ... } 
/*  * Hashtable源码  */ //Hashtable的hash值由key.hashCode()得到,数组下标是先把 (hash & 0x7FFFFFFF) 再对数组长度取模 public synchronized V put(K key, V value) {     ... ...     int hash = key.hashCode();     int index = (hash & 0x7FFFFFFF) % tab.length;     ... ... } 

5. 底层数据结构不同

  • HashMap 在 JDK1.8 中底层数据结构变成了数组加链表加红黑树,数组长度在大于 8 并满足一定条件下升级成红黑树 满足一定条件下升级成红黑树 ,与链表比查找性能上更加优秀。
  • Hashtable 底层采用数组加链表的结构。

6. 扩容方法不同

  • HashMap 在扩容时会生成两个链表或者红黑树,依次插入新数组的 当前坐标当前坐标 + 旧数组长度 的位置。 - HashMap 扩容流程
  • Hashtable 在扩容时每个结点依次做取余运算得到新数组下标。
/*  * Hashtable源码  */ //Hashtable在扩容rehash()时每个结点的新数组下标为 (e.hash & 0x7FFFFFFF) % newCapacity protected void rehash() {     ... ...     for (int i = oldCapacity ; i-- > 0 ;) {         for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {         	... ...             int index = (e.hash & 0x7FFFFFFF) % newCapacity;             ... ...         }     } } 

7. 线程安全问题

版权声明:玥玥 发表于 2021-03-20 2:05:13。
转载请注明:HashMap与Hashtable的八点区别(源码解析) | 女黑客导航