Java面试题:关于HashMap的这些一定不能不会

【知识点记录】- 不能不知道的知识点

😄生命不息,写作不止
🔥 继续踏上学习之路,学之分享笔记
👊 总有一天我也能像各位大佬一样
🏆 博客首页   @怒放吧德德  To记录领地
🌝分享学习心得,欢迎指正,大家一起学习成长!

在这里插入图片描述转发请携带作者信息 @怒放吧德德 @一个有梦有戏的人

HashMap的线程不安全体现在哪?

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

  • 并发修改导致死循环:当多个线程同时对HashMap进行修改操作(如put操作)时,可能会导致HashMap内部的数据结构(如链表)形成环形结构,这会在某些操作中导致死循环。
  • 数据不一致:如果多个线程同时对HashMap进行读写操作,可能会出现一个线程在对HashMap进行修改时,另一个线程正在读取数据,这样可能会读到部分修改的数据,从而导致数据不一致的现象。
  • 扩容问题:在多线程环境下,当HashMap达到扩容条件并进行扩容时,若同时有其他线程进行插入操作,可能会导致部分数据丢失或者结构的不正确。

为了解决这些问题,在多线程环境下推荐使用ConcurrentHashMap,它通过分段锁的机制提高了并发访问的效率,同时也保证了线程安全。

HashMap的扩容条件是什么?

HashMap的扩容条件主要涉及两个参数:容量(capacity)和加载因子(load factor)。容量是哈希表中桶(bucket)的数量,而加载因子是哈希表在其容量自动增加之前可以达到多满的一种度量。
当HashMap中的条目数量超过容量和加载因子的乘积时,HashMap会进行扩容。默认的加载因子是0.75,这意味着当HashMap存储的元素超过了桶的数量的75%时,就会发生扩容。
具体的扩容过程包括:

  • 创建一个新的哈希表,其容量是原来的两倍。
  • 将现有的元素重新计算哈希并放入新的哈希表中。

这个过程通常被称为“rehashing”,因为它涉及重新计算每个元素的哈希码,并在新的桶中进行放置。扩容是一个相对昂贵的操作,因为它涉及到重新计算数组内每个元素的哈希并定位到新的桶中,所以在使用HashMap时要尽量预估容量,以免频繁扩容带来不必要的性能开销。

加载因子是如何与HashMap的容量和冲突概率相关的?

加载因子在HashMap中是控制空间效率(容量利用率)和时间效率(查找/插入/删除操作的速度)之间平衡的一个参数。它直接影响到HashMap的性能,因为它决定了哈希表何时进行扩容操作,即其容量的增加。
这样的设定与以下因素相关:

  • 容量 (Capacity): 这是哈希表能够存储键值对的最大数量。HashMap的容量总是2的幂次。
  • 冲突概率: 当多个键被散列到同一个bucket(或哈希桶)时,会发生冲突。这通常会增加查找特定键时需要比较的键值对数量,因为这些键值对会以链表或红黑树的形式存在于同一哈希桶中。

当添加元素到HashMap的时候,随着元素的不断增加,冲突的概率通常会升高。为了降低冲突的概率,一种办法是增加哈希表的容量(即桶的数量),这样可以使得元素更加分散,减少每个桶中的元素数量,从而减少冲突概率和链表长度。然而,HashMap容量的增加意味着额外的内存开销。因此,加载因子是决定在增加存储元素数量与维持合适空间利用率之间平衡的一个关键因素。

一般情况下,如果加载因子较低,例如0.5或更低,那么哈希表的冲突概率会很小,但空间浪费较大。如果加载因子较高,例如大于0.75,那么空间利用率虽然高,但冲突的概率也会相应增加,这可能会导致更长的链表或更多的红黑树转换操作,降低了操作的效率。因此,根据不同的应用场景选择不同的加载因子是很重要的。在实际开发中,除非有特别的需求,一般会使用HashMap默认的加载因子0.75,因为它提供了时间和空间成本之间较好的平衡。

jdk8中对HashMap做了哪些改变?

  • 红黑树替代链表: 之前版本的HashMap在处理哈希冲突时,会使用链表来存储位于同一个bucket内的元素。在JDK 8中,当特定桶内的链表长度超过阈值(默认情况下为8)时,链表会转换为红黑树,从而改进了最坏情形下的时间复杂度。当存储的元素少于阈值时,红黑树会再转回链表。
  • 改进的哈希函数: JDK 8中的HashMap实现引入了一个更高质量的哈希函数,可以减少冲突,使得键更均匀分布在桶中。这是通过对键的hashCode()的返回值进行进一步的处理来实现的,比如扰动函数,可以将高位的影响也考虑到索引的计算中,从而提高桶的利用率。
  • resize()的优化: JDK 8中对HashMap的resize过程也进行了优化。当HashMap需要增加桶的数量进行resize操作的时候,新的实现减少了元素重新定位时的计算量。这是通过保留原有的哈希桶索引,并仅决定元素是留在原位置还是移动到新的位置(索引+旧容量)的方式来完成的。
  • 并发修改时的计数器: 在JDK 8中,HashMap引入了一个fail-fast机制的计数器,用于快速失败操作。这意味着如果在迭代过程中HashMap结构性发生变化(如增加或删除元素),迭代器会快速失败并抛出ConcurrentModificationException异常,而不是导致不确定的行为。
  • 空位置懒惰初始化: 在先前的JDK版本中,HashMap的构造函数会初始化整个哈希表数组。但在JDK 8中,懒惰初始化被引入,哈希表数组的实际物理空间将在第一次插入操作时才被初始化。这意味着如果HashMap被创建后并没有被使用,可以节省内存空间。
  • Java 8 Stream API的支持: JDK 8中引入的Stream API同样可以用在HashMap当中,允许对Map进行各种流操作,比如过滤、排序或执行其他聚合函数等。

这些改进增强了HashMap在性能和效率方面的表现,尤其是在有大量冲突时的表现以及对内存空间的优化上。


以上就是关于HashMap的一些基础知识点,或许可以的去记忆反而更不容易记住,但是如果当作是无聊的时候翻翻博客去看看,这或许就能够有些深刻。


转发请携带作者信息 @怒放吧德德 @一个有梦有戏的人
持续创作很不容易,作者将以尽可能的详细把所学知识分享各位开发者,一起进步一起学习。
👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍
谢谢支持!

  • 22
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个有梦有戏的人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值