HashMap的常见问题

 关于HashMap的一些常见的问题,自己总结一下:

首选HashMap在jdk1.7和jdk1.8里面的实现是不同的,在jdk1.7中HashMap的底层实现是通过数组+链表的形式实现的,在jdk1.8中HashMap的底层是通过数组+链表+红黑树来实现的。

Question1: 数组链表是怎么切换的(1.7)?

答:在put的时候采用hash(key)&(len-1)来计算数据存放的index,以此存放元素。当出现哈希冲突的时候,因为有限的数组长度,遭遇哈希冲突,此时就可以使用链表来存储哈希值相同但是值不同的对象。

Question2:Entry节点如何插入链表(1.7)?

答: Entry节点插入链表是使用头插法来实现的,主要的实现是通过其CreateEntry来实现的,Entry的构造方法中可以协助头插的顺利进行,使用头插法是考虑到热点数据的问题,当时的想法是最近插入的元素,最近也可能被使用,头插入的实现可以缩短链表查找元素的时间。

Question3:jdk1.8以后为什么改用尾插入?

答:根据initialCapacity*LoadFactoor=capacity以后,如果插入的元素容量达到了capacity,此时会进行扩容,扩容操作按照源码中的写法,主要有两步:1.扩容一个新的Entry,容量为原来的两倍。2.进行Rehash操作,将原来的数据复制到新的Entry中。(1.7)如果是头插入的话,当多线程处理的时候,此时如果存在a->b->c链表,当我们rehash以后,有可能变为b->a,然而其他的线程处理完之后,结果可能会造成b->a->b,造成loop成环。一旦寻找数据会造成死循环。

而1.8以后改成尾插入以后,源码中使用了一个高位来识别之前的数据和插入的新数据,保持了之前的顺序,解决了1.7中可能造成成环的问题。具体的实现是扩容只有最高位会多出一个1,如果之前的数据一旦e & oldCapacity = 0,表明是原来的数据,保持就好,如果是为1,表明是即将插入的新数据,此时保持插入高位,这样就避免了成环的问题。

Question4:为什么要进行Rehash操作?

答: 因为此时的长度遍历按照index=hashcode & (len - 1)的计算,此时的规则变了,所以需要进行rehash操作。

Question5:HashMap为什么不是线程安全?

答:在jdk1.7中即使不出现死循环,由于put操作未加锁,我们也不能确保对于多个线程同时执行put操作时,上一秒修改完的put的值,下一秒get是否是修改后的值,容易被其他线程的值所覆盖,线程安全无法保证(1.7&1.8都是如此)

Question6:为啥源码的容量初始化大小为16?

答:在源码中有个方法叫做tableSizeFor(),这个方法是为了将当前的容量扩容到一个距离当前容量的2的整次方幂。阿里巴巴手册上也建议使用HashMap需要设置一个初始化的容量值,一般来说是设置为16,为什么是16而不是8或者4,因为4或者8容易导致hashMap扩容,影响性能。只要输入的HashCode分布是相对均匀的,那么hash算法就是均匀的,所以给16主要也是为了实现均匀分布。

Question7:为啥重写equals方法的同时需要重写hashCode方法?使用HashMap举例子说明?

答: 首先,equals方法继承了Object的equals方法,比较的对于值对象比较的是两个对象的值是否相等,对于引用对象比较的是两个对象的内存地址是否相等。上面也说到HashMap是通过hash(key)&(len-1)去寻找index的,index相同就形成链表存储数据,但是假如一个index中存储了object1、object2对象,他们的hash值相同,此时如果我们get数据object2,get(key)此时将会有两个值,你怎么能确保获取到的是object2而不是object1,此时就需要使用equals来比较对象的key是否相同,这样才可以获取到对的对象。实质上在底层get方法的实现是通过getNode(hash(key),key)来实现的,前面的是hash值,后面的是equals对比的对象。

Question8:HashMap线程不安全不适合多线程,那用什么可以替代?

答:可以使用HashTable或者ConcurrentHashMap来替代HashMap,但是HashTable仅仅是使用synchronized来实现同步锁,从而使得线程安全,但是并发度不高。所以一般使用ConcurrentHashMap来替代

Question9:为什么在1.8中当链表长度为8时转换为红黑树,数据长度为6时转化为链表?

答: 首先从时间复杂度方面来分析的话,当长度为6时查找的平均长度为6/2=3,(底层会判断偏后还是偏前,以此来从后或者从前遍历),而红黑数log6=2.6,如果为8的时候,此时8/2=4,而log8为3,至于具体这样选,是一种空间和时间的权衡。

posted @ 2019-12-14 20:54  前进山峦  阅读(344)  评论(0编辑  收藏  举报