HashMap的十个连环追问,阁下又该如何应对?

在一个月黑风高的夜晚,你接到了一个公司的面试邀请,去参加面试。

你穿好西装,打好领带,梳好发型,自信满满地走进写字楼。

面试官问:

  • HashMap你熟悉吗?答:嗯,我经常用的。

  • 说说你在哪些场景下用的?答:嗯。。让我想想哈。。。

  • 今天的面试就到这里吧,出门左转就是电梯。

大家好,我是徒手敲代码,今天来介绍一下面试中最常考的集合之一,HashMap。

概念很简单,就是可以通过key,来找到这个key对应的value。key的类型常用 String 或者 Integer,因为这两个类型是用 final 修饰,是不可变的。关键考察对 HashMap 的底层数据结构,以及 put 的一些相关流程。

问题

下面列举出 HashMap 最常问的十一个问题以及对应的答案,建议先看问题,先思考,然后再看答案。

1、平时在哪些地方用到 HashMap 呢?(场景题)

2、说说 HashMap 的底层数据结构?

3、为什么一定要用红黑树呢?

4、为什么选择 8 之后转为红黑树呢?链表转为红黑树之后,还会继续转为链表吗?

5、说一下 put 的流程?(可以说说 JDK 为了效率更高,做了哪些优化)

6、多线程情况下,put 是线程安全的吗?(可以简单举个例子,说一下哪里不安全)

7、如果我想要让 HashMap 变成线程安全的,你觉得可以怎么做?

8、头插法会导致死循环,那你觉得在以前的版本中,为什么会使用头插法呢?

9、说说 HashMap 的扩容操作?

10、在实际开发当中,如何避免频繁扩容?

答案

1、HashMap 是一个用来存放key-value键值对的集合类型,key和value的类型都可以自定义,一般key会用String或者Integer类型,因为这两个类型的对象的值不会发生变化,它们对应的哈希值会比较固定,在获取的时候不用每次都重新计算一遍。 场景举例:加载配置文件,可以将配置的参数以键值对的形式放入HashMap中,便于通过键名随时获取配置值。

2、在jdk1.8,HashMap 用的是数组+链表+红黑树的数据结构。没有发生哈希冲突的时候,元素都放在数组里面;发生了冲突之后,数组的同一个位置需要放多个元素,此时数组的位置放的就是一个链表或者一个红黑树,这个位置的元素小于8并且 HashMap 的元素数量小于64的时候,用的是链表,这样可以防止过于频繁的树化和反树化;当元素多的时候,就把链表变成红黑树;

3、用红黑树代替链表,是因为元素多了之后,链表的查询效率会很低;红黑树是一种自平衡二叉查找树。这意味着即使在多次插入、删除等操作后,红黑树也能通过特定的旋转和颜色调整操作保持其左右子树的高度大致平衡。这种平衡性保证了在最坏情况下,从根节点到任意叶子节点的最长路径不超过最短路径的两倍。实际上红黑树只是一个兜底的方案,用于应对可能出现的哈希冲突严重、链表过长导致性能下降的特殊情况。在大多数正常运行的 HashMap 实例中,这种情况并不常见,链表作为主要数据结构已经能够提供高效的服务。

4、元素少的时候,链表在空间和效率上,都比红黑树要好。至于为什么是8,因为这个时候,链表的长度符合泊松概率的分布,长度大于8的概率是小于百万分之一,也就是说,一般情况下,很难发生链表转换成红黑树这种情况,发明这个机制更多是为了防止在特殊情况下,HashMap 依然可以有一个比较好的性能。

5、put 的时候,先计算出 key 的哈希值,再计算出在数组上的对应位置,如果这个位置没元素,就直接放在数组的这个位置,数组没初始化长度的话,要先创建一个默认长度为 16 的数组。如果这个位置有元素,就看看这个位置上的所有key的值,有没有跟 put 进来的这个key 一样的,如果有,就把原来的 value 替换成 put 进来的 value;如果没有,就放在数组对应位置的链表或者红黑树上,如果放链表,要判断一下是否要转换成红黑树,最后判断一下需不需要扩容 两个方面的优化,为了提高计算效率,用位运算代替取模运算;为了减少哈希冲突,用多个与运算符对哈希值的高位和低位进行搅动运算,尽量做到,就算两个key只有一个位不一样,都会对最后的哈希值造成影响

6、不安全。jdk1.8 使用尾插法的方式,解决了链表死循环的问题,但是,同一时间多个线程同时 put,如果碰巧判断到同一个槽位是空的,那么后面插入的数据,会把前面的覆盖掉

7、直接在每个方法前面都加个 synchronized,HashTable 就是这样实现线程安全的,但是效率很低,相当于只能单线程操作。可以使用ConcurrentHashMap,它使用 CAS 和分段锁的方式,可以在保证线程安全的同时,提高并发的效率

8、因为通常情况下,认为新插入的数据,会有更大的概率被用到,放前面的话查询效率会更高,不用遍历链表,直接在第一个就能拿到

9、HashMap 会在其容量达到阈值时自动进行扩容。当HashMap的实际大小等于或超过这个阈值时,将会触发扩容操作。HashMap 默认的负载因子是 0.75,这是在空间利用率和哈希冲突概率,两者之间的一个权衡,是测试人员做了大量实验之后,得出的一个效果比较好的结果。链表转换为红黑树之后,如果红黑树的结点数变成6,那么这个红黑树会退化成链表

10、可以根据业务需要,计算出 HashMap 会放多少个元素在里面,然后指定一个初始化长度

今天的分享到这里结束了,如果你喜欢这种分享知识的方式,可以在下方留言喔。你的支持,是我创作的最大动力!


关注微信公众号”徒手敲代码“,回复“电子书”,免费获取大佬推荐的Java书籍💪

  • 26
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值