Map接口实现类体系图
HashMap和HashTable的异同
-
继承的父类不同
HashMap继承自AbstractMap类,Hashtable继承自Dictionary类,但二者都实现了Map接口。Dictionary类是一个已经被废弃的类(见其源码中的注释)。
-
底层实现
HashMap:Node[ ]数组+链表+红黑树
HashTable: Entry[ ]数组+链表
这里还要说一些东西,
HashMap中新添加节点在某一索引位产生哈希冲突,会优先放置在链表的末尾,因为当链表达到一定长度就要变成红黑树,此后在该索引位新添加的节点就会根据树化方式放置在末尾
HashTable中新添加的节点在某一索引位产生哈希冲突,会优先放置在链表的头节点,也就是说每次冲突都会让索引位先指向新添加的节点,而后让新添加节点内的next指针指向之前链表的头节点,完成后新添加的节点就会变成链表头节点。
-
线程是否安全
HashMap线程不安全
HashTable线程安全
javadoc中关于hashmap的一段描述如下:
此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。
Hashtable 中的方法大多是Synchronize的,而HashMap中的方法在一般情况下是非Synchronize的。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用HashMap时就必须要自己增加同步处理。HashTable实现线程安全的代价就是效率变低,因为会锁住整个HashTable,而ConcurrentHashMap做了相关优化,因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定,效率比HashTable高很多。
HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。
hashmap的put方法调用putVal()方法,假如A线程和B线程同时对同一个数组索引位调用putVal()方法,两个线程会同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作,造成A的写入操作丢失。故解决方法就是使用ConcurrentHashMap。
HashMap的迭代器(HashIterator)是fail-fast的,故当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException异。
Hashtable的迭代器(enumerator)迭代器不是fail-fast的。但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
-
遍历方式
HashMap只支持Iterator遍历,而HashTable支持Iterator和Enumeration两种方式遍历
-
contains方法,toString()方法
HashMap是没有contains方法的,但有containsValue和containsKey方法,没有重写toString
hashtable则保留了contains方法,同containsValue,并包括containsValue和containsKey方法,重写了toString()方法
-
是否允许null值
Hashmap是允许key和value为null值的,用containsValue和containsKey方法判断是否包含对应键值对;HashTable键值对都不能为空,否则报空指针异常。
-
计算索引值方式
为了得到元素的位置,首先需要根据元素的 KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置。
①:HashMap有个hash()方法重新计算了key的hashCode,目的是为了有效避免hash冲突:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
注意这里计算hash值,先调用hashCode方法计算出一个hash码,再将hash码与右移16位的hash码相异或,从而得到新的hash值。
在求hash值对应的位置索引时,index = (n - 1) & hash。将哈希表的大小固定为了2的幂,因为是取模得到索引值,故这样取模时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
②:Hashtable通过key的hashCode()方法得到的hash值就为最终hash值。
在求hash值位置索引时计算index的方法:
int index = (hash & 0x7FFFFFFF) % tab.length;
&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数
因此hashtable的哈希冲突概率会比hashmap高很多
-
默认初始容量,个性化初始容量
HashMap 的初始容量为:16,Hashtable 初始容量为:11。两者的默认负载因子都是0.75。
这里有一点也要注意, HashMap有tableSizeFor()方法,而HashTable没有该方法,因此:
对于 HashMap,如果自定义初始容量>=0,最后哈希表的容量为比自定义容量大并且最接近自定义容量的2的n次幂,如果自定义初始容量<0,则会报错