IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 面试HashMap你都扛不住,还想拿到offer? -> 正文阅读

[数据结构与算法]面试HashMap你都扛不住,还想拿到offer?

当我们面试Java开发岗位时,面试官问的频率出现最多的问题,就是这个HashMap,不管是传统型公司还是互联公司,HashMap是必问的,所以作者爆肝整理了HashMap的23个问题以及答案,请查收!

1、你知道HashMap的数据结构吗?

HashMap底层是基于数组 + 链表实现的,不过在 jdk1.7 和 1.8 中具体实现稍有不同

HashMap采用Entry数组来存储key-value对,每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体。只是在JDK1.8中,链表长度大于8的时候,链表会转成红黑树!

2、什么是Hash冲突,如何解决Hash冲突?

哈希函数的设计至关重要,好的哈希函数会尽可能地保证计算简单和散列地址分布均匀,但是我们需要清楚的是数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法(数组+链表的形式)。

3、用LinkedList代替数组结构可以吗?有什么优缺点?

因为用数组效率最高!在HashMap中,定位桶的位置是利用元素的key的哈希值对数组长度取模得到。此时,我们已得到桶的位置。显然数组的查找效率比LinkedList大;

4、HashMap何时扩容以及它的扩容机制?

如果bucket满了(超过load factor*current capacity),就要resize。load factor为0.75,为了最大程度避免哈希冲突current capacity为当前数组大小

5、为何HashMap的数组长度一定是2的次幂?

因为2的n次方实际就是1后面n个0,2的n次方-1,实际就是n个1。

例如长度为8时候,3&(8-1)=3??2&(8-1)=2,不同位置上,不碰撞。

而长度为5的时候,3&(5-1)=0??2&(5-1)=0,都在0上,出现碰撞了。

所以,保证容积是2的n次方,是为了保证在做(length-1)的时候,每一位都能&1,保证地址散列均匀分布

6、说一下HashMap在1.7中put元素的过程??

  • 判断当前数组是否需要初始化。
  • 如果 key 为空,则 put 一个空值进去。
  • 根据 key 计算出 hashcode。
  • 根据计算出的 hashcode 定位出所在桶。
  • 如果桶是一个链表则需要遍历判断里面的 hashcode、key 是否和传入 key 相等,如果相等则进行覆盖,并返回原来的值。
  • 如果桶是空的,说明当前位置没有数据存入;新增一个 Entry 对象写入当前位置

  • 当调用 addEntry 写入 Entry 时需要判断是否需要扩容。
  • 如果需要就进行两倍扩充,并将当前的 key 重新 hash 并定位。
  • 而在 createEntry 中会将当前位置的桶传入到新建的桶中,如果当前桶有值就会在位置形成链表。

7、说一下HashMap中get元素的过程??

  • 首先也是根据 key 计算出 hashcode,然后定位到具体的桶中。
  • 判断该位置是否为链表。
  • 不是链表就根据 key、key 的 hashcode 是否相等来返回值。
  • 为链表则需要遍历直到 key 及 hashcode 相等时候就返回值。
  • 啥都没取到就直接返回 null

8、说一下HashMap在1.8中put元素的过程?

过程跟1.7差不多,但是多了一些判断:

  • 当前链表的大小是否大于预设的阈值,大于时就要转换为红黑树;
  • 如果当前桶已经为红黑树,那就要按照红黑树的方式写入数据;

9、说一下HashMap在1.8中get元素的过程?

  • 首先将 key hash 之后取得所定位的桶。

  • 如果桶为空则直接返回 null 。

  • 否则判断桶的第一个位置(有可能是链表、红黑树)的 key 是否为查询的 key,是就直接返回 value。

  • 如果第一个不匹配,则判断它的下一个是红黑树还是链表。

  • 红黑树就按照树的查找方式返回值。

  • 不然就按照链表的方式遍历匹配返回值。

10、HashMap在JDK8做了哪些优化?

  • 由数组+链表的结构改为数组+链表+红黑树。
  • 优化了高位运算的hash算法:h^(h>>>16)
  • 扩容后,元素要么是在原位置,要么是在原位置再移动2次幂的位置,且链表顺序不变。

11、为什么在解决Hash冲突的时候,不直接用红黑树?而是选择优先使用链表,再转红黑树?

  • 因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要;
  • 当元素小于8个当时候,此时做查询操作,链表结构已经能保证查询性能;
  • 当元素大于8个的时候,此时需要红黑树来加快查询速度,但是新增节点的效率变慢了;
  • 如果一开始就用红黑树结构,元素太少,新增效率又比较慢,无疑这是浪费性能的;

12、不用红黑树用二叉树可以吗?

可以。但是二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。

13、当链表转换成红黑树后,什么时候退化为链表?

  • 扩容 resize()时,红黑树拆分成的树的结点数小于等于临界值6个,则退化成链表。
  • 移除元素 remove()时,在removeTreeNode()方法会检查红黑树是否满足退化条件,与结点数无关。如果红黑树根root为空,或者root的左子树/右子树为空,root.left.left根的左子树的左子树为空,都会发生红黑树退化成链表。

14、HashMap在并发条件下会有什么问题?

  • 多线程扩容,引起的死循环问题
  • 多线程put的时候可能导致元素丢失
  • put非null元素后get出来的却是null

15、在JDK8中还有这些问题吗?

在jdk1.8中,死循环问题已经解决。其他两个问题还是存在

16、HashMap键可以是Null吗?

必须可以,key为null的时候,hash算法最后的值以0来计算,也就是放在数组的第一个位置。

17、你一般使用什么作为HashMap的key?

一般用Integer、String这种不可变类当HashMap当key,而且String最为常用。

  • 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。
  • 因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的,这些类已经很规范的覆写了hashCode()以及equals()方法。

18、当使用对象作为HashMap的Key时有什么问题吗?

要重写equals和hashcode方法。否则会出现以下问题:

hashcode可能发生改变,导致put进去的值,无法get出,如下所示

?输出值如下:

19、HashMap是线程安全的吗?如何实现线程安全?

HashMap是线程不安全的。

实现方式:

  • 通过Collections.synchronizedMap()来封装所有不安全的HashMap的方法,就连toString, hashCode都进行了封装,就是为每一个方法添加了synchronized关键字进行修饰。使用的是的synchronized方法,是一种悲观锁.在进入之前需要获得锁,确保独享当前对象,然后做相应的修改/读取。方式简单粗暴,但是效率低。
  • 使用ConcurrentHashMap。只有在需要修改对象时,比较和之前的值是否被人修改了,如果被其他线程修改了,那么就会返回失败,是一种无锁的实现。基于CAS实现,类似于乐观锁机制。ConcurrentHashMap采用了"锁分段"策略,ConcurrentHashMap的主干是一个一个Segment组,在ConcurrentHashMap中,一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,并发环境下,对于不同Segment的数据进行操作是不用考虑锁竞争的,对于同一个Segment的操作才需考虑线程同步。理论上就允许16个线程并发执行。

20、请谈谈ConcurrentHashMap底层实现原理?

在JDK7中ConcurrentHashMap采用了"锁分段"策略,ConcurrentHashMap的主干是一个一个Segment组,在ConcurrentHashMap中,一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,并发环境下,对于不同Segment的数据进行操作是不用考虑锁竞争的,对于同一个Segment的操作才需考虑线程同步。理论上就允许16个线程并发执行。

21、ConcurrentHashMap的size()方法实现原理?

  • 要统计整个ConcurrentHashMap的元素个数,可以将每个Segment的count相加,count是volatile变量,可以保证读到的是最新值,但count可能会在累加过程中发生改变,导致结果不正确。
  • ConcurrentHashMap采用HashMap中的“快速失败”机制,即设置一个modCount变量,在put,remove,clean方法中都让modCount++,先尝试两次通过不对Segment加锁的方式统计Size,若发现前后的modCount不一致,则说明容器大小发生了变化,此时再通过锁住所有Segment的put,remove,clean方法计算count。

22、ConcurrentHashMap中put过程?

因为volatile不保证原子性,所以在put操作中需要对Segment加锁。

put操作分为两步:

  • 是否需要扩容
  • 在插入元素前先判断Segment里的HashEntry数组是否超过容量(cap*loadFactor),如果超过阈值,就进行扩容。值得一提的是,在HashMap中,是先插入元素后再检查是否达到容量,有可能造成扩容之后再也没有新元素插入,造成空间浪费。
  • 举个例子,在ConcurrentHashMap中,现有元素正好等于容量,那么就先判断是否超过容量(没有超过),那么添加新元素(此时超出容量一个元素,但没有扩容)。而如果是HashMap,则先插入这个元素,发现超出容量,于是扩容,可再也没有新的元素添加进来了,于是造成了浪费。
  • 定位元素位置
  • 遍历HashEntry链表,找到对应元素位置并更新

23、HashMap和HashTable的区别?

  • HashMap基于数组和链表实现。不考虑Hash冲突的情况下,仅需一次定位就能找到元素。比如在新增元素的时候,通过Hash函数将元素定位Hash表中某个位置,直接将数据存入到该地址上,当我们查找或者删除元素,可以直接通过Hash函数定位到该数据。但是没有什么事情都是完美的,如果两个不同的元素,通过哈希函数得出的实际存储地址相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。HashMap采用了链地址法,也就是数组+链表的方式。把相同Hash值的数据放在了链表上。当HashMap中的链表出现越少,性能才会越好。当发生哈希冲突并且size大于阈值的时候,需要进行数组扩容,扩容时,需要新建一个长度为之前数组2倍的新的数组,然后将当前的Entry数组中的元素全部传输过去,扩容后的新数组长度为之前的2倍,所以扩容相对来说是个耗资源的操作。HashMap继承自AbstractMap,HashMap允许key、value为空。HashMap默认容量是16,且负载因子是0.75。HashMap是线程不安全的,效率高。
  • HashTable和HashMap的实现原理几乎一样,HashTable不允许key和value为null;HashTable是线程安全的。但是HashTable线程安全的策略实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。

以上是整理的比较全面的HashMap面试题,大家记住答案的同时,最好还是理解其原理!!!?????

?

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-09-14 13:36:45  更:2021-09-14 13:36:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 3:51:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码