集合
HashMap
1.7 1.8区别
1.7头插法 1.8尾插法
JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,那么他们为什么要这样做呢?因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题
结构不同
JDK1.7的时候使用的是数组+ 单链表的数据结构。但是在JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(logN)提高了效率)
扩容机制
扩容后数据存储位置的计算方式不同
扩容后数据存储位置的计算方式也不一样:1. 在JDK1.7的时候是直接用hash值和需要扩容的二进制数进行&(这里就是为什么扩容的时候为啥一定必须是2的多少次幂的原因所在,因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞)(hash值 & length-1)
线程不安全
1.7
先分析在jdk1.7中的问题,相信大家都知道在jdk1.7多线程环境下HashMap容易出现死循环,主要原因就是因为上文提到的1.7中hashmap因为扩容问题导致的死循环
1.8
在jdk1.8中对HashMap进行了优化,在发生hash碰撞,不再采用头插法方式,而是直接插入链表尾部,因此不会出现环形链表的情况,但是在多线程的情况下仍然不安全
(线程A会把线程B插入的数据给覆盖,发生线程不安全)
2的幂次
提出问题:为什么hashMap的容量扩容时一定是2的幂次
答:因为HashMap是通过哈希算法得出哈希值之后,才决定将键值对放入哪个索引,如果分配不均,增加了碰撞的几率,减慢了查询的效率,造成空间的浪费。2的幂-1都是11111结尾的,所以碰撞几率小。
重写equals必须重写Hashcode
equals比较规则就是比较两个对象的内存地址。而hashcode是本地方法,java的内存是安全的,因此无法根据散列码得到对象的内存地址,但实际上,hashcode是根据对象的内存地址经哈希算法得来的
现在有两个Student对象:
Student s1=new Student(“小明”,18);
Student s2=new Student(“小明”,18);
假如只重写equals而不重写hashcode,那么Student类的hashcode方法就是Object默认的hashcode方法,由于默认的hashcode方法是根据对象的内存地址经哈希算法得来的,显然此时s1!=s2,故两者的hashcode不一定相等。
然而重写了equals,且s1.equals(s2)返回true,根据hashcode的规则,两个对象相等其哈希值一定相等,所以矛盾就产生了,因此重写equals一定要重写hashcode,而且从Student类重写后的hashcode方法中可以看出,重写后返回的新的哈希值与Student的两个属性有关。
以上就是HashMap的面试分析图的解析