HashMap面试笔记整理

HashMap面试笔记整理

1.HashMap在jdk1.8以前和以后的区别

​ 1):jdk1.8以前,hashMap的底层实现是数组+链表,它的缺点就是即使哈希函数用的再好,也很难达到百分百均匀分布,而且当很多元素放在一个桶中时,链表会变得很长,此时遍历的时间复杂度为O(n),jdk1.8以前,HashMap采用的是头插法,如果插入时,数组位置中已经有了元素,jdk1.8以前会插入到数组中,原始节点变成新节点的后续节点,插入时,先判断是否需要扩容,然后再插入。扩容时,1.8以前需要对原数组中的元素进行重新的hash定位带行的数组位置

​ 2):jdk1.8以后,hashMap的底层实现是数组+链表+红黑树,若桶中的链表个数超过8个,就会变成红黑树,遍历查找,由于使用红黑树,此时时间复杂度为O(logn),使用性能上得到了提升。jdk1.8以后,HashMap采用的是尾插法,如果插入时,数组位置中已经有了元素,jdk1.8以后会遍历数组,将元素放在最后,插入时,先插入再扩容。扩容时,1.8以后采用的是简单的判断逻辑,位置不变或索引+旧容量大小

2.HashMap数据插入原理

​ 1):判断数组是否为空,为空就初始化

​ 2):不为空就计算出k的hash值,通过(n-1)& hash计算出应当存放在数组的下标 index

​ 3):查看table[index]是否存在数据,没有数据就构建一个node节点存放在table[index]中

​ 4):如果有数据,说明出现了hash冲突,然后判断key值是否相等,如果相等就替换原来的value值

​ 5):如果不相等,判断当前节点是不是树型节点,如果是树型节点,就创建树型节点插入到红黑树中

​ 6):如果不是树型节点,就创建普通的node节点加入链表中,判断链表长度是否大于8,并且数组长度大于64,大于的话链表变为红黑树

​ 7):插入完成后要判断节点数是否大于阈值,如果大于,则扩容数组为原来的2倍

3.HashMap怎么设定初始容量大小的

​ 一般new HashMap() 不传值得话,默认容量为16负载因子为0.75,如果自己传一个值K,那么初始值为大于K的2的整数次方,例如:K=7,那么默认容量就是8(2的3次方),如果K=10,那么默认容量就是16(2的4次方).

4.HashMap扩容机制
1):什么时候扩容

​ 当容器添加元素时,会判断元素的个数,如果大小等于阈值(阈值=数组长度*负载因子 例如长度16的数组的阈值为:16X0.75=12,即元素个数大于等于12时就会自动扩容)时就会自动扩容

2):jdk1.7和jdk1.8扩容的区别

​ jdk1.7):如果原有table长度已经达到了上限,就不再扩容了。

​ 如果还未达到上限,则创建一个新的table,并调用transfer方法,

​ transfer方法的作用是把原table的Node放到新的table中,使用的是头插法,也就是说,新table中链表的顺序和旧列表中是相反 的(例如:原数组是3–>5–>7,新数组就是7–>5–>3),在HashMap线程不安全的情况下,这种头插法可能会导致环状节点(例如:A线程在插入节点B,B线程也在插入,遇到容量不够开始扩容,重新hash,放置元素,采用头插法,后遍历到的B节点放入了头部,这样形成了环)。

​ jdk1.8):新数组长度是原数组长度的两倍(如果原数组是2的n次倍,新数组就是2的n+1次倍);

​ 新数组和原数组链表顺序一致(原数组是3–>5–>7,新数组还是3–>5–>7);

​ 正常情况下,计算节点在table中的下标的方法是:hash&(oldTable.length-1),扩容之后,table长度翻倍,计算table下标的方法是hash&(newTable.length-1),也就是hash&(oldTable.length*2-1),于是我们有了这样的结论:这新旧两次计算下标的结果,要不然就相同,要不然就是新下标等于旧下标加上旧数组的长度

5.HashMap线程安全问题

​ 1):HashMap是线程不安全的,多线程情况下:jdk1.7会出现死循环,数据丢失,数据覆盖的问题jdk1.8会出现数据覆盖的问题(以1.8为例,当A线程判断index位置为空后正好挂起,B线程开始往index位置的写入节点数据,这时A线程恢复现场,执行赋值操作,就把A线程的数据给覆盖了;还有++size这个地方也会造成多线程同时扩容等问题。)

​ 2):线程安全的map集合有:HashTable,Collections.synchronizedMap,ConcurrentHashMap(使用最广泛)

HashTable:HashTable是直接在操作方法上加synchronized关键字,锁住整个数组,粒度比较大。

Collections.synchronizedMap:是使用Collections集合工具的内部类,通过传入Map封装出一个SynchronizedMap对象,内部定义了一个对象锁,方法内通过对象锁实现;

ConcurrentHashMap:使用分段锁,降低了锁粒度,让并发度大大提高。

6.ConcurrentHashMap的分段锁的实现原理

​ ConcurrentHashMap成员变量使用volatile 修饰,免除了指令重排序,同时保证内存可见性,另外使用CAS操作和synchronized结合实现赋值操作,多线程操作只会锁住当前操作索引的节点。

7.链表转红黑树的阈值是多少,红黑树转链表的阈值是多少

​ 链表转红黑树的阈值是8,红黑树转链表的阈值是6

8.HashMap内部节点是否有序

​ 1):无序,根据hash值随机插入

​ 2):有序的Map集合有哪些:LinkedHashMap 和 TreeMap

LinkedHashMap如何实现有序:

​ LinkedHashMap内部维护了一个单链表,有头尾节点,同时LinkedHashMap节点Entry内部除了继承HashMap的Node属性,还有before 和 after用于标识前置节点和后置节点。可以实现按插入的顺序或访问顺序排序。

TreeMap如何实现有序:

​ TreeMap是按照Key的自然顺序或者Comprator的顺序进行排序,内部是通过红黑树来实现。所以要么key所属的类实现Comparable接口,或者自定义一个实现了Comparator接口的比较器,传给TreeMap用于key的比较。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值