1、继承的父类不同
Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口
2、线程安全性不同
javadoc中关于hashmap的一段描述如下:此实现不是同步的。如果多个线程同时访问一个哈希映射, 而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。
Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。
在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用
HashMap时就必须要自己增加同步处理。(结构上的修改是指添加或删除一个或多个映射关系的任
何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射
的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap方
法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问
3、是否提供contains方法
HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方
法容易让人引起误解。
Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和
containsValue功能相同。
4、key和value是否允许null值
其中
key
和
value
都是对象,并且不能包含重复
key
,但可以包含重复的
value
。
Hashtable
中,
key
和
value
都不允许出现
null
值。但是如果在
Hashtable
中有类似
put(null,null)的操作, 编译同样可以通过,因为
key
和
value
都是
Object
类型,但运行时会抛出
NullPointerException异常,这是 JDK
的规范规定的。
HashMap
中,
null
可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为
null
。当
get()方法返 回
null
值时,可能是
HashMap
中没有该键,也可能使该键所对应的值为
null
。因此,在
HashMap中不能由 get()
方法来判断
HashMap
中是否存在某个键, 而应该用
containsKey()
方法来判断。
5、两个遍历方式的内部实现上不同
Hashtable
、
HashMap
都使用了
Iterator
。而由于历史原因,
Hashtable
还使用了
Enumeration
的方
式 。
6、hash值不同
哈希值的使用不同,
HashTable
直接使用对象的
hashCode
。而
HashMap
重新计算
hash
值。
7、内部实现使用的数组初始化和扩容方式不同
HashTable
在不指定容量的情况下的默认容量为
11
,而
HashMap
为
16
,
Hashtable不要求底层数组的容量一定要为
2
的整数次幂,而
HashMap
则要求一定为
2
的整数次幂。
Hashtable
扩容时,将容量变为原来的
2
倍加
1
,而
HashMap
扩容时,将容量变为原来的2倍。
Hashtable
和
HashMap
它们两个内部实现方式的数组的初始大小和扩容的方式。
HashTable
中hash 数组默认大小是
11
,增加的方式是
old*2+1
。
至于为什么HashMap要求底层数组的容量一定要为2的整数次幂?
在 JDK1.7 中整个扩容过程就是一个取出数组元素(实际数组索引
位置上的每个元素是每个独立单向链表的头部,也就是发
生 Hash 冲突后最后放入的冲突元素)然后遍历以该元素为
头的单向链表元素,依据每个被遍历元素的 hash 值计算其
在新数组中的下标然后进行交换(
即原来 hash 冲突的单向
链表尾部变成了扩容后单向链表的头部)。
在 JDK 1.8 中 HashMap 的扩容操作就显得更加的骚气了,
由于扩容数组的长度是 2 倍关系,所以对于假设初始
tableSize = 4 要扩容到 8 来说就是 0100 到 1000 的变化
(左移一位就是 2 倍),
在扩容中只用判断原来的 hash 值
与左移动的一位(newtable 的值)按位与操作是 0 或 1 就
行,0 的话索引就不变,1 的话索引变成原索引加上扩容前
数组