面试

HashMap

JDK7和JDK8中的HashMap有什么区别?

  • JDK7中的HashMap,是基于数组+链表来实现的,它的底层维护一个Entry数组。它会根据计算的hashCode将对应的key-value键值对存储到该数组中,如果发生hashCode冲突,会将该key-value键值采用头插法插入已有元素的前面, 形成了一个链式的存储结构。

  • 采用头插***导致的问题 -> 并发情况下的扩容死链:扩容的过程 resize 方法又调用了一个 transfer 的方法,把里面的一些 Entry 进行了一个 rehash, 在这个过程中可能会造成链表的一个循环,会在下一个 get 的时候出现一个死循环的情况

  • JDK7中HashMap的实现方案有一个明显的缺点,即当Hash冲突严重时,在桶上形成的链表会变得越来越长,这样在查询时的效率就会越来越低,其时间复杂度为O(N)。

  • JDK8中的HashMap,是基于数组+链表+红黑树来实现的,它的底层维护一个Node数组。当链表的存储的数据个数大于等于8且数组的长度大于等于64时,不再采用链表存储,而采用了红黑树存储结构。这么做主要是在查询的时间复杂度上进行优化,链表为O(N),而红黑树一直是O(logN),可以大大的提高查找性能。

  • 1.7 是大于等于阈值且没有空位时才扩容,而 1.8 是大于阈值就扩容

  • 1.8 在扩容计算 Node 索引时,会优化

介绍一下HashMap底层的实现原理

  • 它基于 hash 算法,通过 put 方法和 get 方法存储和获取对象。
  • 存储对象时,我们将 key-value 传给 put 方法时,它调用 key 的 hashCode 计算 hash 从而得到桶位置,进一步存储,HashMap 会根据当前桶的占用情况自动调整容量。获取对象时,我们将 key 传给 get 方法,它调用 hashCode 计算 hash 从而得到桶位置,并进一步调用equals()方法确定键值对。

介绍一下HashMap的扩容机制

  • 在初始化 HashMap 的时候,如果没有指定容量,默认初始容量是 16 , 负载因子是 0.75 ,根据容量和负载因子计算出扩容的阈值,在 put 的时候判断当前的 size 是否大于阈值,大于阈值的话会扩容成原来的两倍大小

为什么容量选择 2 的次方

  • 在进行下标计算时可以用 & (size - 1) 代替 % size ,提升一定的计算性能
  • 扩容后重新计算桶下标时 二次 hash 值 & 原始容量为 0 的留在原位,不为 0 的移到新的位置,新位置为 原始位置 + 原始容量

树化意义

  • 红黑树用来避免 DoS 攻击,防止链表超长时性能下降,树化应当是偶然情况,是保底策略
  • hash 值如果足够随机,则在 hash 表内按泊松分布,在负载因子 0.75 的情况下,长度超过 8 的链表出现概率是 0.00000006,树化阈值选择 8 就是为了让树化几率足够小

索引计算

  • 首先,计算对象的 hashCode()再进行调用 HashMap 的 hash() 方法进行二次哈希二次 hash() 是为了综合高位数据,让哈希分布更为均匀,最后 & (capacity – 1) 得到索引

扩容(加载)因子为何默认是 0.75f

  • 在空间占用与查询时间之间取得较好的权衡,大于这个值,空间节省了,但链表就会比较长影响性能,小于这个值,冲突减少了,但扩容就会更频繁,空间占用也更多

多线程下的问题

  • 没有加锁,在多线程的情况下不能保证他的数据是安全的,就是 put 进去的值不再是原来的那个值

key 的设计要求

  • HashMap 的 key 可以为 null,但 Map 的其他实现则不然
  • 作为 key 的对象,必须实现 hashCode 和 equals,并且 key 的内容不能修改(不可变)
  • key 的 hashCode 应该有良好的散列性
全部评论

相关推荐

2025-12-27 18:11
已编辑
门头沟学院 前端工程师
28双非鼠鼠第一份实习,感谢金山,感谢面试官张先生的赏识,也感谢自己很开心很开心(有没有待过的前辈,求摸鱼技巧bushi)timeline12.15 投递12.16 约面12.18 一面 半个小时后约二面12.19 二面,口头oc12.24 发offer一面1. 开发页面中使用的布局方式2. flex: 1 是什么的缩写3. 水平居中的方法4. tailwindcss 的优势5. js 的闭包6. 打印结果的题,解释为什么(var 定义 i ,setTimeout 执行打印),使用 let 的打印结果7. 箭头函数和普通函数的区别8. promise 构造函数是同步还是异步9. 内存泄漏的情况10. interface 和 type 的区别11. react 的 key 作用12. 常用的钩子函数13. 怎么避免不必要的渲染14. useeffect 的使用场景15. react 和 vue 怎么选择16. vue 的 data 为什么用函数17. tcp 为什么需要三次握手和四次挥手18. vite 为什么比较快19. 解释防抖节流和手写防抖函数,还有实现思路20. 深浅拷贝的区别和手写深拷贝,讲实现思路反问了业务,反馈时间和学习建议二面基本上是围绕项目展开,根据项目的每一项,来给场景题问你会怎么做,跟基础相关的东西如下:1. 虚拟列表的实现和原理2. zustand 和 context 的区别3. vitest 相关,写测试的话应该怎么做些什么?4. monorepo的细节问题5. 做项目的动机6. 事件委托和时间冒泡的区别有个点顺着问了我五个问题实在是答不下去了就是说感觉金山云这边面试虽然一面全是八股,但是二面还是要好好准备项目,做到能被深挖那么两三个问题的程度,鼠鼠也是运气很好,问的都是准备过的嘻嘻面试完之后还很期待这个面试官会不会是我mt或者ld,会很认真的听我说话,然后告诉我哪里有小问题,不知道是不是鼠鼠的错觉,感觉他看后辈的眼神都是带有欣赏的意味真的很复合我对mt/ld的幻想(bushi),但是后来发现他ip是北京的qwq有点点小失落,不过没关系,看隔壁某书感觉金山的节奏还挺慢的期待入职ing愿一切顺利,好运常伴吾身这里再吐槽一下流程,怎么!!这么!!慢!!急死我了急死我了!!鬼知道我从周一到接到offer这段时间有多煎熬,哎呀但是但是好在一切如愿
发面经攒人品
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务