這個從源碼中能夠直接看出來,HashMap 繼承自 AbstractMap,而 Hashtabl 繼承自 Dictionary。html
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable
Hashtable 在不少方法定義時都會加上 synchronized
關鍵字,說明 Hashtabl 是線程安全的,而 HashMap 並不能保證線程安全。java
public synchronized int size(); public synchronized boolean isEmpty(); public synchronized boolean contains(Object o); public synchronized V get(Object key); public synchronized V put(K key, V value); ...
在 Hashtable 添加元素源碼中,咱們能夠發現,若是添加元素的 value 爲 null 時,會拋出 NullPointerException。在程序內部,有這樣一行代碼 int hash = key.hashCode
,若是添加的 key 爲 null 時,此時也會拋出空指針異常,所以,在 Hashtable 中,是不容許 key 和 value 爲 null 的。算法
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; }
而在 HashMap 的 put 方法中,調用了 putVal
方法(1.8 版本中),該方法須要有一個 int 類型的 hash
值,這個值是利用內部的 hash
方法產生的。從下面的源代碼能夠看出,當 key 爲 null 時,返回的 hash 值爲 0,說明在 HashMap 中是容許 key=null 的狀況存在的。數組
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict){ } static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
從 HashMap 的 API 能夠看出,它只包含 containsKey 和 containsValue 方法。而 Hashtable 還包含了 contains 方法。安全
HashMap 中把 contains 方法去掉的緣由主要它容易引發混淆,不如 containsKey 和 containsValue 表達的準確。this
而 Hashtable 中 contains 方法也是調用 containsKey 方法來實現的。線程
public boolean contains(Object o) { return containsKey(o); }
Hashtable 初始容量爲 11,默認的負載因子爲 0,.75。HashMap 定義了兩個常量在對容器進行初始化會用到,能夠看到其初始容量爲 16,默認的負載因子也是爲 0.75.指針
//---------------------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); } //---------------------HashMap----------------------------- /** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /** * The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f;
Hashtable 擴容時調用 rehash
方法,增長容器容量的代碼在下面。從中能夠看出,最終的容量是 newCapacity
,若是該變量在沒有大於 MAX_ARRAY_SIZE
(靜態變量,內部定義爲 Integer.MAX_VALUE - 8) 以前,都是按照 oldCapacity*2 + 1 的速度增長的。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];
在 HashMap 中,擴容主要是經過 resize
方法實現的,其擴容的代碼是這樣的 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
,可見是跟 newCap 變量有關,在正常狀況下,newCapa 是按照 oldCap<<1
的速度,即每次長度變爲原來的兩倍增加的。htm
if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); }
本段話摘自 http://www.cnblogs.com/alexlo/archive/2013/03/14/2959233.html
HashMap 的迭代器(Iterator) 是 fail-fast 迭代器,而 Hashtable 的 enumerator 迭代器不是 fail-fast 的。因此當有其它線程改變了 HashMap 的結構(增長或者移除元素),將會拋出 ConcurrentModificationException,但迭代器自己的 remove() 方法移除元素則不會拋出 ConcurrentModificationException 異常。但這並非一個必定發生的行爲,要看 JVM 。這條一樣也是 Enumeration 和 Iterator 的區別。關於 fail-fast 機制能夠查看這篇文章。
一下下這段分析摘自:http://www.javashuo.com/article/p-sxehvotn-dc.html hash 算法是將元素定位到相對應桶的位置上,在 Hashtable 中,是這樣實現 hash 算法的。由於 Hashtable 中,其桶擴容以後長度爲奇數,這種方式的哈希取模會更加均勻(這點仍是不清楚爲何)。
int hash = key.hashCode(); // hash 不能超過 Integer.MAX_VALUE,因此要取其最小的 32 個 bit int index = (hash & 0x7FFFFFFF) % tab.length;
在 JDK 1.8 版本中,HashMap 的 hash 方法以下。
static final int hash(Object key){ int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } final V putValu(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); ... }
異或
運算,返回結果。(n-1)&hash
獲取該對象的鍵在 hashmap 中的位置。(其中 hash 就是經過 hash 方法得到的值),n 表示 hash 桶數組的長度,而且該長度爲 2 的 n 次方。一般聲明 map 集合時不會指定大小,或者初始化的時候就建立一個容量很大的map 對象,因此這個經過容量大小與 key 值進行 hash 的算法在開始的時候只會對低位進行計算,雖然容量的 2 進制高位一開始都是 0,可是 key 的 2 進制高位一般是有值的,所以先在 hash 方法中將 key 的 hashCode 右移 16 位在與自身異或,使得高位也能夠參與hash,更大程度上減小了碰撞率。