面经之HashMap

- 介绍下 HashMap 的底层数据结构吧

现在用的都是 JDK 1.8,底层是由“数组+链表+红黑树”组成,如下图,而在 JDK 1.8 之前是由“数组+链表”组成
红黑树作用:主要是为了提升在 hash 冲突严重时(链表过长)的查找性能,使用链表的查找性能是 O(n),而使用红黑树是 O(logn)。
什么时候用链表?什么时候用红黑树?
对于插入,默认使用链表节点。当同一个索引位置的节点在新增后达到
9个(阈值8,阈值为8是在时间和空间上权衡的结果) :如果此时数组长度大于等于 64,则会触发链表节点转红黑树节点(treeifyBin);而如果数组长度小于64会进行扩容。

红黑树节点大小约为链表节点的2倍,在节点太少时,红黑树的查找性能优势并不明显
但是:转回链表节点是用的6 ,防止当节点个数在8徘徊时,就会频繁进行红黑树和链表的转换,造成性能的损耗。

HashMap 有哪些重要属性?分别用于做什么的?

  1. size :HashMap 已经存储的节点个数;
  2. threshold:扩容阈值,当 HashMap 的个数达到该值,触发扩容。(在我们新建 HashMap 对象时, threshold 还会被用来存初始化时的容量。HashMap 直到我们第一次插入节点时,才会对 table 进行初始化,避免不必要的空间浪费。)
  3. loadFactor:负载因子,扩容阈值 = 容量 * 负载因子。 负载因子默认值是0.75

HashMap 的默认初始容量是多少 16 (why:6是2的N次方,并且是一个较合理的大小。如果用8或32,也是OK的,主要根据使用情况)
HashMap 的容量有什么限制吗?容量必须是2的N次方,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方
容量在hashmap内部怎么算的

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

|=(或等于) 两个二进制数按位取或
->>>(无符号右移)
cap - 1为了处理 cap 本身就是 2 的N次方的情况
计算机底层是二进制的,移位和或运算是非常快的,所以这个方法的效率很高

HashMap 的容量必须是 2 的 N 次方,这是为什么?
计算索引位置的公式为:(n - 1) & hash,当 n 为 2 的 N 次方时,n - 1 为低位全是 1 的值,此时任何值跟 n - 1 进行 & 运算的结果为该值的低 N 位,达到了和取模同样的效果,实现了均匀分布。当 n 不为 2 的 N 次方时,hash 冲突的概率明显增大。
在这里插入图片描述
JDK 1.8 主要进行了哪些优化

  1. 底层数据结构从“数组+链表”改成“数组+链表+红黑树”,主要是优化了 hash 冲突较严重时,链表过长的查找性能:O(n) -> O(logn)。
  2. 计算 table 初始容量的方式发生了改变,老的方式是从1开始不断向左进行移位运算,直到找到大于等于入参容量的值;新的方式则是通过**“5个移位+或等于运算**”来计算
  3. 优化了 hash 值的计算方式,新的只是简单的让高16位参与了运算
  4. 扩容时插入方式从“头插法”改成“尾插法”,避免了并发下的死循环
  5. 扩容时计算节点在新表的索引位置方式从“h & (length-1)”改成“hash & oldCap”,性能可能提升不大,但设计更巧妙、更优雅。
    扩容时计算节点在新表的索引位置方式从“h & (length-1)”改成“hash & oldCap”,性能可能提升不大,但设计更巧妙、更优雅。

在这里插入图片描述

原文链接:https://blog.csdn.net/v123411739/article/details/106324537

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java中的Map接口提供了一种将键映射到值的对象。其中一种实现是HashMap,它使用哈希表来实现。HashMap允许 null 键和 null 值,并且没有顺序保证。HashMap 的操作复杂度为 O(1) 平均复杂度,因此在许多情况下非常高效。 ### 回答2: Java集合类中的Map用来保存键-值对,HashMap是其中的一种实现方式。HashMap的内部实现是基于哈希表的,它可以将任意对象作为键,并且保证键的唯一性。在HashMap中,键和值都允许为null,但是同一个HashMap中,键不能重复,如果重复了,新的value会覆盖旧的value。 HashMap内部是通过hashCode()和equals()方法来判断键是否相等的。当我们向HashMap中添加一个键-值对时,系统会先计算出键的hashCode值,然后用该值来确定存放该键值对的位置。如果有多个键的hashCode值相同,称为哈希冲突,那么HashMap会在这些键值对所在的位置上,以链表的形式组成一个链表存储。 HashMap的优点在于插入、删除和查找都比较快,时间复杂度均为O(1),对于大量的数据,它的效率优于List或Set等集合。但是,在内存使用上,HashMap会比List或Set等集合耗费更多的内存空间,因为它需要额外的空间来存储哈希值和链表指针。 值得注意的是,在多线程环境下,HashMap是不安全的,会出现并发修改导致的数据不一致问题,这时可以使用ConcurrentHashMap或者加锁机制来保证线程安全。 总之,HashMap是Java中非常实用的集合类,适用于大量键值对的存储和检索。我们应该了解HashMap的内部实现原理,并且在使用过程中需要注意其线程安全性等问题。 ### 回答3: Java中的Map是一种键值对的集合,其中每个元素都由一个键和一个值组成。在Map中,每个键必须是唯一的,而值可以重复。 在Map中,HashMap是最常用的实现类之一。它是基于哈希表实现的,可以通过键快速查找值。哈希表是一种支持常数时间快速查找的数据结构,因为它可以将键映射到与其对应的值的位置,因此查找时只需要计算键的哈希码即可找到对应的值。 HashMap的实现类中使用了两个数组来存储键值对:一个数组用于存储键,另一个数组用于存储值。当插入键值对时,首先会将键的哈希码计算出来,然后通过哈希码将键映射到键数组的位置,将值存储在值数组的相同位置。当需要查找一个键时,只需计算其哈希码并定位到键数组的位置,然后从值数组中取出对应的值即可。 与集合一样,HashMap也是线程不安全的,因此在多线程环境下需要使用ConcurrentHashMap或通过synchronized关键字来保证线程安全性。此外,在使用HashMap时应该尽量避免使用null作为键,因为null的哈希码为0,可能与其他非null键的哈希码相同,导致哈希碰撞,影响HashMap的性能。 总之,HashMap是一种高效的键值对集合,通过哈希表实现快速的查找和插入操作。在正确使用和注意安全性的前提下,使用HashMap可以大大提升代码效率和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值