之前聊StringBuffer和StringBuilder的区别时候,顺便提到了HashTable和HashMap。实际上,除了线程安全之外,它们之间还有其他很多区别。这个也算个老话题了,在面试界算是老题目了。知乎上也有这方面的讨论:HashMap和Hashtable的区别。那么,它们的区别到底在哪,实际应用场景有什么分别呢?老方法,翻源码。
Hashtable的继承和实现关系:
* @author Arthur van Hoff
* @author Josh Bloch
* @author Neal Gafter
* @see Object#equals(java.lang.Object)
* @see Object#hashCode()
* @see Hashtable#rehash()
* @see Collection
* @see Map
* @see HashMap
* @see TreeMap
* @since JDK1.0
*/
public class Hashtable
extends Dictionary
implements Map, Cloneable, java.io.Serializable {
HashMap的继承和实现关系:* @author Doug LeaHashtable是从JDK1.0开始就出现、而HashMap是JDK1.2才出现。Hashtable继 承Dictionary,而HashMap继承AbstaractMap类,通常认为Dictionary类太老,而AbstaractMap较新。(W3C关于dictionary的描述:Dictionary类已经过时了。在实际开发中,你可以实现Map接口来获取键/值的存储功能。)Dictionary是从JDK1.0开始就有的抽象类,内部只有几个简单的抽象方法,Hashtable继承Dictionary后重写这些抽象方法;AbstaractMap是从JDK1.2开始才出现的,具有很多已经实现的方法,更加完善更加好用。
* @author Josh Bloch
* @author Arthur van Hoff
* @author Neal Gafter
* @see Object#hashCode()
* @see Collection
* @see Map
* @see TreeMap
* @see Hashtable
* @since 1.2
*/
public class HashMap
extends AbstractMap
implements Map, Cloneable, Serializable
{HashMap的put方法:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entrye = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
Hashtable的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 = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entrye = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entrye = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
可以看到Hashtable不允许value为空,而HashMap没有这限制;HashMap在key为Null的时候,不会再对key进行hash,而是直接调用putForNullKey方法:private V putForNullKey(V value) {
for (Entrye = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
putForNullKey方法会遍历table表,如果有key为null,就更新掉value,否则创建新的键值对插入。Hashtable则无差别对待,所以在对key进行hash的时候会报空指针。
HashMap非线程安全与Hashtable线程安全
这块是很直观的区别了。Hashtable大量使用了synchronized来保证同步,而HashMap是简单的异步处理。
hash过程的区别
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);
useAltHashing = sun.misc.VM.isBooted() &&
(initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
}
HashMap的构造函数:
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity <0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacitycapacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
Hashtable的initialCapacity 是11,而HashMap的initialCapacity 是16
Hashtable插入数据容量不够时候,增加的方式是 old*2+1modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
调用rehash方法: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);
boolean currentAltHashing = useAltHashing;
useAltHashing = sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = currentAltHashing ^ useAltHashing;
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entryold = oldMap[i] ; old != null ; ) {
Entrye = old;
old = old.next;
if (rehash) {
e.hash = hash(e.key);
}
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
HashMap简单粗暴:直接2倍void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
Hashtable计算hash值,是直接获取key的hashcodeprivate int hash(Object k) {
if (useAltHashing) {
if (k.getClass() == String.class) {
return sun.misc.Hashing.stringHash32((String) k);
} else {
int h = hashSeed ^ k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
} else {
return k.hashCode();
}
}
而HashMap重新计算hash值,而且用与代替求模:final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
其他区别
引用知乎:
HashTable有一个contains(Object value)方法,功能上与containsValue(Object value)一样,但是在实现上花销更大,现在已不推荐使用。而HashMap只有containsValue(Object value)方法。HashTable 使用Enumeration,HashMap使用Iterator。Iterator其实与Enmeration功能上很相似,只是多了删除的功能。用 Iterator不过是在名字上变得更为贴切一些。模式的另外一个很重要的功用,就是能够形成一种交流的语言(或者说文化)。有时候,你说 Enumeration大家都不明白,说Iterator就都明白了。