不是我吹,这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 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

相关推荐
nice_lcj52018 小时前
Java Lambda 表达式入门:怎么写、怎么用、常用方法有哪些
java·开发语言·后端·面试
MUSHANGXS18 小时前
大模型时代的新蓝海-基于OS底层的AI-Agent应用
人工智能·职场和发展·交互
老师我太想进步了202620 小时前
【数据结构-4】算法美学之顺序表
数据结构·算法
风影同志7832420 小时前
编码器是激活函数吗?—— 一个看起来很像、其实不是的类比
算法
UIU11420 小时前
P1481 魔族密码
c++·算法·动态规划
nice_lcj5201 天前
ThreadLocal 入门:它是怎么隔离数据的、为什么会导致内存泄漏
java·开发语言·后端·面试·并发编程·内存泄漏·threadlocal
加宝的起点雷达1 天前
40 条候选里 38 条来自同一个源,主卡整屏重样
人工智能·算法·创业
秋天的怀念_David1 天前
算法一千零一夜 · 第二十六夜 协议的母语
java·spring boot·算法