不是我吹,这8道HashMap面试题让你面试时对答如流

前言

又到了一年一度的金三银四面试季,我们拿着自己的面试秘籍去面试,但是面试官的问题五花八门,让我们摸不清他们的套路。今天我就总结了面试时必问的hashmap面试题,无论面试官怎么问,我们都对答如流。

另外本人整理了20年至23年的面试题大全,包含spring、并发、数据库、Redis、分布式、dubbo、JVM、微服务等方面总结,下图是部分截图,需要的话点这里点这里,暗号CSDN。

1.JDK8中的HashMap与JDK7的HashMap有什么不一样?

  1. JDK8中新增了红黑树,JDK8是通过数组+链表+红黑树来实现的
  2. JDK7中链表的插入是用的头插法,而JDK8中则改为了尾插法
  3. JDK8中的因为使用了红黑树保证了插入和查询了效率,所以实际上JDK8中
    的Hash算法实现的复杂度降低了
  4. JDK8中数组扩容的条件也发了变化,只会判断是否当前元素个数是否查过了
    阈值,而不再判断当前put进来的元素对应的数组下标位置是否有值。
  5. JDK7中是先扩容再添加新元素,JDK8中是先添加新元素然后再扩容

2.HashMap中PUT方法的流程?

  1. 通过key计算出一个hashcode
  2. 通过hashcode与"与操作"计算出一个数组下标
  3. 在把put进来的key,value封装为一个entry对象
  4. 判断数组下标对应的位置,是不是空,如果是空则把entry直接存在该数组位
  5. 如果该下标对应的位置不为空,则需要把entry插入到链表中
  6. 并且还需要判断该链表中是否存在相同的key,如果存在,则更新value
  7. 如果是JDK7,则使用头插法
  8. 如果是JDK8,则会遍历链表,并且在遍历链表的过程中,统计当前链表的元
    素个数,如果超过8个,则先把链表转变为红黑树,并且把元素插入到红黑树中

3.JDK8中链表转变为红黑树的条件?

  1. 链表中的元素的个数为8个或超过8个
  2. 同时,还要满足当前数组的长度大于或等于64才会把链表转变为红黑树。为什么?因为链表转变为红黑树的目的是为了解决链表过长,导致查询和插入效率慢的问题,而如果要解决这个问题,也可以通过数组扩容,把链表缩短也可以解决这个问题。所以在数组长度还不太长的情况,可以先通过数组扩容来解决链表过长的问题。

4.HashMap扩容流程是怎样的?

  1. HashMap的扩容指的就是数组的扩容, 因为数组占用的是连续内存空间,
    所以数组的扩容其实只能新开一个新的数组,然后把老数组上的元素转移到新
    数组上来,这样才是数组的扩容
  2. 在HashMap中也是一样,先新建一个2被数组大小的数组
  3. 然后遍历老数组上的没一个位置,如果这个位置上是一个链表,就把这个链
    表上的元素转移到新数组上去
  4. 在这个过程中就需要遍历链表,当然jdk7,和jdk8在这个实现时是有不一样
    的,jdk7就是简单的遍历链表上的没一个元素,然后按每个元素的hashcode结
    合新数组的长度重新计算得出一个下标,而重新得到的这个数组下标很可能和
    之前的数组下标是不一样的,这样子就达到了一种效果,就是扩容之后,某个
    链表会变短,这也就达到了扩容的目的,缩短链表长度,提高了查询效率
  5. 而在jdk8中,因为涉及到红黑树,这个其实比较复杂,jdk8中其实还会用到
    一个双向链表来维护红黑树中的元素,所以jdk8中在转移某个位置上的元素
    时,会去判断如果这个位置是一个红黑树,那么会遍历该位置的双向链表,遍
    历双向链表统计哪些元素在扩容完之后还是原位置,哪些元素在扩容之后在新
    位置,这样遍历完双向链表后,就会得到两个子链表,一个放在原下标位置,
    一个放在新下标位置,如果原下标位置或新下标位置没有元素,则红黑树不用
    拆分,否则判断这两个子链表的长度,如果超过八,则转成红黑树放到对应的
    位置,否则把单向链表放到对应的位置。
  6. 元素转移完了之后,在把新数组对象赋值给HashMap的table属性,老数组
    会被回收到。

5.为什么HashMap的数组的大小是2的幂次方数?

JDK7的HashMap是数组+链表实现的

JDK8的HashMap是数组+链表+红黑树实现的

当某个key-value对需要存储到数组中时,需要先生成一个数组下标index,并且这个

index不能越界。

在HashMap中,先得到key的hashcode,hashcode是一个数字,然后通过

hashcode & (table.length - 1) 运算得到一个数组下标index,是通过与运算计算出

来一个数组下标的,而不是通过取余,与运算相比于取余运算速度更快,但是也有一

个前提条件,就是数组的长度得是一个2的幂次方数。

6、常见的 HashMap 的迭代方式

在实际开发过程中,我们对于 HashMap 的迭代遍历也是常见的操作,HashMap 的迭代遍历常用方式有如下几种:

  • 方式一:迭代器模式

    Map<String, String> map = new HashMap<>(16);
    Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
    while (iterator.hasNext()) {
    Map.Entry<String, String> next = iterator.next();
    System.out.println(next.getKey() + ":" + next.getValue());
    }

  • 方式二:遍历 Set>方式

    Map<String, String> map = new HashMap<>(16);
    for (Map.Entry<String, String> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
    }

  • 方式三:forEach 方式(JDK8 特性,lambda)

    Map<String, String> map = new HashMap<>(16);
    map.forEach((key, value) -> System.out.println(key + ":" + value));

  • 方式四:keySet 方式

    Map<String, String> map = new HashMap<>(16);
    Iterator<String> keyIterator = map.keySet().iterator();
    while (keyIterator.hasNext()) {
    String key = keyIterator.next();
    System.out.println(key + ":" + map.get(key));
    }

把这四种方式进行比较,前三种其实属于同一种,都是迭代器遍历方式,如果要同时使用到 key 和 value,推荐使用前三种方式,如果仅仅使用到 key,那么推荐使用第四种。

7.为什么说HashMap是线程不安全的?

答:HashMap在多线程并发时线程不安全,主要表现在下面两个方面:

(1) 当向HashMap中put(添加)元素时导致的多线程数据不一致

比如有两个线程 A 和 B ,首先 A 希望插入一个 key-value键值对到HashMap 中,它首先计算记录所要落到的 hash 桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程 A 的时间片用完了,而此时线程 B 被调度得以执行,和线程 A 一样执行,只不过线程 B 成功将记录插到了桶里面。假设线程 A 插入的记录计算出来的 hash 桶索引和线程 B 要插入的记录计算出来的 hash 桶索引是一样的,那么当线程 B 成功插入之后,线程 A 再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程 B 插入的记录,这样线程 B 插入的记录就凭空消失了,造成了数据不一致的行为。

简单来说就是在多线程环境下,向HashMap集合中添加元素会存在覆盖的现象,导致了线程不安全。

(2) 当HashMap进行扩容调用resize()函数时引起死循环

HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。

HashMap的线程不安全主要体现在下面两个方面:

1.在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况。

2.在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况。

8.HashMap 的工作原理是什么?

一,存储方式: Java中的HashMap是以键值对(key-value)的形式存储元素的。

二,调用原理: HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。

HashMap的实现原理:

  1. 利用key的hashCode重新hash计算出当前对象的元素在数组中的下标

  2. 存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中

  3. 获取时,直接找到hash值对应的下标,再进一步判断key是否相同,从而找到对应值。

  4. 理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

最后:

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

下面的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2020至2023年收集的一些大厂的面试真题(都整理成文档,小部分截图),有需要的可以点击进入暗号CSDN

相关推荐
捕鲸叉26 分钟前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer30 分钟前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
wheeldown1 小时前
【数据结构】选择排序
数据结构·算法·排序算法
观音山保我别报错2 小时前
C语言扫雷小游戏
c语言·开发语言·算法
TangKenny3 小时前
计算网络信号
java·算法·华为
景鹤3 小时前
【算法】递归+深搜:814.二叉树剪枝
算法
iiFrankie3 小时前
SCNU习题 总结与复习
算法
鱼跃鹰飞4 小时前
大厂面试真题-简单说说线程池接到新任务之后的操作流程
java·jvm·面试
Dola_Pan4 小时前
C++算法和竞赛:哈希算法、动态规划DP算法、贪心算法、博弈算法
c++·算法·哈希算法
小林熬夜学编程5 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法