老生常谈的问题——Hashtable和HashMap有什么区别java
你们通常都能说出几条,好比Hashtable是线程安全的,不支持null做为key和value值等等。那么,要仔细了解这个问题仍是直接从Hashtable的源码入手。数组
先列一下我找到的区别:安全
首先从最上方的注释中能够看到Hashtable自JDK1.0版本就有了,而HashMap是JDK1.2才加入的。观察一下类的声明,咱们能够看到他们继承的类也是不一样的,Hashtable继承的是Dictionary,Dictionary这个类从注释上写着已是obsolete被废弃了,因此连带Hashtable也基本不用了。Hashtable也有元素个数,数组大小,负载因子这些属性,不用元素个数用的是count不是size。也是使用链表法来解决冲突。 多线程
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
构造函数能够看出默认大小是11,同时初始大小给定多少初始数组就多大,不会作扩展到2的指数次幂这样的操做。threshold=initialCapacity*loadFactor这点和HashMap相同。并发
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() { this(11, 0.75f); }
contains这个方法是从表尾开始向前搜索的,同时也没有使用==来比较函数
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能够看出,Hashtable的index计算逻辑是使用key.hashCode()的后31位而后除以tab.length取余数。HashMap的那种按位与的操做仅当操做数低位全是1时才等价为取余操做,也就是2的指数次幂-1才可成立,这样作计算速度比除法快不少,不过冲突数量会增长,因此加入了一些打散的设计好比hashCode高位与低位异或。性能
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; }
扩展方法rehash的扩大方式是旧数组大小*2+1,而HashMap是*2,要从新计算每个的index因此效率低,同时冲突时将后面的元素插入到前面元素的前一位,因此会改变顺序 this
protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1;//新大小=旧大小*2+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;//从新计算每个元素的index e.next = (Entry<K,V>)newMap[index];//先后元素有冲突时,后面的元素插入到前面元素的前面 newMap[index] = e; } } }
对于插入结点一样要先检查是否存在key值相同的点,存在则不插入,而后检查是否须要扩展数组,插入时若是发生冲突,也是将要插入的元素放在链表的首位,而putVal方法是放入尾部的。同时,能够看到Hashtable是不支持null做为key或value值的spa
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) {//value为null直接报错 throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode();//若key为null这里会报错 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++; }
Hashtable的hashcode方法计算全部entry的hash值总和线程
public synchronized int hashCode() { int h = 0; if (count == 0 || loadFactor < 0) return h; // Returns zero loadFactor = -loadFactor; // Mark hashCode computation in progress Entry<?,?>[] tab = table; for (Entry<?,?> entry : tab) { while (entry != null) { h += entry.hashCode(); entry = entry.next; } } loadFactor = -loadFactor; // Mark hashCode computation complete return h; }
elements这个方法是Hashtable多出来的,返回全部value值的枚举
public synchronized Enumeration<V> elements() { return this.<V>getEnumeration(VALUES); }
咱们能够注意到,Hashtable的方法都加上了synchronized,他们是线程安全的,可是对于自己是线程安全的状况就会大幅度影响性能,JDK8开始引入modCount来做为fast-fail机制,防止其余线程的非synchronzied方法对Hashtable进行修改。