以下回答适合面试忽悠面试官,但能不能忽悠通过就看面试官问的深不深啦,如果想要深入了解更多,建议多看看源码。。。
===========================================================================================
在jdk1.8之前,HashMap,HashTable,ConcurrentHashMap都采用数组加链表存储数据,使用链地址法解决Hash冲突的问题。
由于HashMap没有使用任何锁机制,所以它的效率是最高的,但是在并发情况下,在需要扩容时,多线程并发进行链表容易产生死循环,导致get()时一直循环查找next节点,引发程序死循环。
HashTable虽然添加了synchronized同步锁,但它是在所有的方法上都使用锁,锁住了整个HashTable对象,所以虽然是线程安全的,但效率极低。
ConcurrentHashMap使用分段锁技术,每一段(Segment)使用了可重入锁(ReentrantLock),每次读写时,先hash定位到对应的Segment段,再hash定位到HashEntry节点,有点像分布式缓存服务的方案,定位到segment,读的时候是不加锁的,写到时候会对该segment段数据加锁,以提高多线程下的数据安全性。
===========================================================================================
在jdk1.8中,HashMap,HashTable,ConcurrentHashMap虽然依然采用数组加链表存储数据,但在链表的长度超过8时,会将链表转化为红黑树存储,这样的好处就是在hash冲突频繁时,保证查找时最坏的情况下时间复杂度为O(logN),并且解决了并发情况下可能死循环的问题,但依然是线程不安全的。 同之前一样依然使用链地址法解决Hash冲突的问题(这应该是性能最好的Hash冲突解决方案了吧)。
1.8中需要注意的是,要想更好的利用红黑树的特性,需要对实现键对象的Comparable接口,使其能快速使用红黑树结构化存储数据。但一般情况下我们不必要考虑这么多,因为在我们的使用hashMap,键通常为String对象,String类已经帮我们实现好了Comparable接口。
HashMap,HashTable除了存储结构发生了变化,其他原理都与jdk1.8之前一致。
ConcurrentHashMap在jdk1.8中放弃了分段锁,采用为数组中每个节点元素加锁,并简化了节点定位hash算法,虽然会导致hash冲突的几率增大,但由于在链表结构大于8时,采用了红黑树,所以查找时的时间复杂度提高了很多。读取元素时依然是不加锁的,但在写操作时,放弃了ReentrantLock锁,对定位到的数组节点使用synchronized同步锁。可见在这个时候synchronized的性能已经优化到与ReentrantKLock锁性能相差不大了。
============================================================================================
jdk1.7和jdk1.8的公共特性:
HashMap允许存在一个键为null,多个value值为空的情况,HashTable,ConcurrentHashMap则不允许存在键和值为空的情况。
HashMap的扩容,一般初始化HashMap时可以传递一个容量大小和负载因子(缺省值为0.75),hashMap采用懒加载,在第一次调用put方法是才会计算size大小并初始化。值得注意的是,当我们自定义HashMap初始容量大小时,构造函数并非直接把我们定义的数值当做HashMap容量大小,而是把该数值当做参数调用方法tableSizeFor,然后把返回值作为HashMap的初始容量大小,该值是大于二的切是二的倍数,目的是可以快速移位得带hash值,当hashMap的元素总数大于阀值会,会扩容一倍,
若threshold(阈值)不为空,table的首次初始化大小为阈值,否则初始化为缺省值大小16。
。。。
以上总结待完善中,欢迎大家补充,更改。。。