继承的父类不同
Hashtable继承自Directory,HashMap继承自AbstractMap类。但二者都实现了Map接口。
线程安全性不同
Hashtable中的方法是Synchronized的,而HashMap中的方法是非Synchronized的。在多线程并发的环境下,可以直接使用Hashtable,不需要为它的方法实现同步,但使用HashMap时就必须要自己添加同步处理。
这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用Collections.synchronizedMap方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问,如下:
Map m = Collections.synchronizedMap(new HashMap(…));
是否存在contains()方法
HashMap把Hashtable中的contains()方法去掉了,改成containsValue()和containsKey()方法,因为contains()方法容易让人引起误解。
Hashtable则保留了contains(),containsKey(),containsValue()三个方法,其中contains(),containsValue()功能相同。
key和value是否允许null值
其中key和value都是对象,并且不能包含重复的key,但可以包含重复的value。
通过Hashtable和HashMap中containsKey和containsValue方法的源码我们可以明显地看出:
Hashtable中contains()方法
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
Entry tab[] = table;
for (int i = tab.length ; i-- > 0 ;) { //双层for循环全局搜索,速度非常慢
for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}
Hashtable中,key和value都不允许出现null值。
HashMap中,null可以作为键这样的键只有一个,可以有多个键所对应的值为null。当get()方法返回null值,可能是HashMap中没有该键,也可能使该键所对应的值为null。
两个遍历方式的内部实现上不同
HashMap和Hashtable都使用了Iterator。由于历史原因,Hashtable还使用了Enumration的方式。
Hashtable中的getEnumeration()和getIterator()方法
private <T> Enumeration<T> getEnumeration(int type) {
if (count == 0) {
return Collections.emptyEnumeration();
} else {
return new Enumerator<>(type, false);
}
}
private <T> Iterator<T> getIterator(int type) {
if (count == 0) {
return Collections.emptyIterator();
} else {
return new Enumerator<>(type, true);
}
}
hash值不同
哈希值的使用不同,Hashtable直接使用对象的hashCode(),而HashMap重新计算hash值。
hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值。
Hashtable在求位置索引时用与运算,且这里一般先用hash&0x7FFFFFFF后,再对length取模,&0x7FFFFFFF目的是为了将负的hash值转换成正值。
内部实现使用的数组扩容和扩容方式不同
Hashtable在不指定容量的情况下默认容量为11,而HashMap为16,HashTable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
Hashtable扩容时,将容量变为原来的2倍+1,而HashMap扩容时,将容量变为原来的2倍。
Hashtable扩容rehash():
protected void rehash() { //当容量增加之后需要对所有元素进行rehash存储位置随即发生改变
int oldCapacity = table.length;
Entry<K,V>[] 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<K,V>[] newMap = new Entry[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
boolean rehash = initHashSeedAsNeeded(newCapacity);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
if (rehash) {
e.hash = hash(e.key);
}
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}