当前位置: 首页 > news >正文

HashMap相关面试题

详见语雀:
https://www.yuque.com/xingsujtpw/gmrbgh/pv0u9pvyb36lcgs2?singleDoc#
HashMap相关面试题

4.3面试题-说一下HashMap的实现原理?
#我的表述 采用键值对, 键是Object对象, 而值又是数组索引下标映射对象 --上述是错误的 #自己重新表述一下 **

HashMap的数据结构: 底层使用hash表数据结构,即数组 + 链表或 数组 + 红黑树

  1. 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
  2. 存储时,如果出现hash值相同的key,此时有两种情况。

a. 如果key相同,则覆盖原始值;

b. 如果key不同(出现冲突),则将当前的key-value放入链表或红黑树中

  1. 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。

面试官追问:HashMap的jdk1.7和jdk1.8有什么区别

#我的表述 HashMap的jdk1.7只有链表, 而jdk1.8不仅有链表, 还有红黑树
  • JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
  • jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8) 时并且数组长度达到64时,将链表转化为红黑树,以减少搜索时间。扩容 resize( ) 时,红黑树拆分成的树的结点数小于等于临界值6个,则退化成链表
4.4面试题-HashMap的put方法的具体流程
#我的表述 第一, 实例化HashMap 第二, 使用key的hashCode重新hash计算当前对象的元素在数组中的下标 第三, 存储值时, 会出现hash相同的key两种情况, 若相同则覆盖原来的值, 若不同即hash冲突, 则将其存储到对应的链表中 #自己重新表述一下 **

第一, 判断数组是否为空(懒加载)

第二, 计算数组下标 : (n - 1) & hash

第三, 判断该槽位有没有数据

第四, 槽位为空, 直接put进去

第五, 槽位不为空, 判断key是否相同

第六, 判断是不是红黑树节点

第七, 是链表, 遍历链表, 尾部插入

第八, 判断size是否超过阈值

业务场景:用HashMap缓存菜品列表,避免重复查数据库

需求:运营后台频繁查看"起售中的菜品列表",每次都要查数据库,很慢。用HashMap做缓存,第一次查完存起来,后面直接从缓存取。

DishService

@ServicepublicclassDishService{// 缓存:key是状态(1=起售),value是该状态下的菜品列表privateHashMap<Integer,List<Dish>>dishCache=newHashMap<>();publicList<Dish>getDishesByStatus(Integerstatus){// 1. 先从缓存取List<Dish>dishList=dishCache.get(status);if(dishList==null){// 2. 缓存没有,查数据库dishList=dishMapper.getByStatus(status);// 3. 放入缓存 ← ✅这里触发HashMap的put流程dishCache.put(status,dishList);log.info("从数据库查询菜品,状态:{}",status);}else{log.info("从缓存获取菜品,状态:{}",status);}returndishList;}}
4.5面试题-讲一讲HashMap的扩容机制
#我的表述 数组不为空扩容 红黑树拆分后扩容 链表超过8,数组长度大于64扩容
  • 在添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次扩容都是达到了扩容阈值(数组长度 * 0.75)
  • 每次扩容的时候,都是扩容之前容量的2倍;
  • 扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新的数组中
    • 没有hash冲突的节点,则直接使用 e.hash & (newCap - 1) 计算新数组的索引位置
    • 如果是红黑树,走红黑树的添加,把树拆分成两个小树。拆分后如果小树节点少于6个,就变回链表,否则保持红黑树
    • 如果是链表,则需要遍历链表,可能需要拆分链表,判断(e.hash & oldCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上
4.6面试题-hashMap的寻址算法
#我的表述 获取值时, 直接找到hash值对应的下标, 在进一步判断key是否相同, 从而找到对应的值 -- 部分正确
  • 第一,首先获取key的hashCode值,然后右移16位 异或运算 原来的hashCode值, 主要作用是使原来的hash值更加均匀, 减少hash冲突

  • 第二,计算数组下标,用(n-1) & hash代替hash % n。因为位与运算比取模快,但要求数组长度必须是2的幂次方。

关于hash值的其他面试题:为何HashMap的数组长度一定是2的次幂?

#我的表述 无, 没想到
  1. 计算索引时效率更高:如果是 2 的 n 次幂可以使用位与运算代替取模
  2. 扩容时重新计算索引效率更高: hash & oldCap == 0 的元素留在原来位置 ,否则新位置 = 旧位置 + oldCap
4.7面试题-hashmap在1.7情况下的多线程死循环问题
#我的表述 hashMap1.8之前采用的是拉链法, 拉链法的意思是数组 + 链表, 若遇到hash冲突, 则把新增的数据添加到链表中 --只能算做了解jdk1.7, 而不是答案所问

因为jdk1.7的hashMap在数组进行扩容的时候, 链表插入采用头插法, 在进行数据迁移的过程中, 则可能导致死循环.

(下面例子不懂问AI)比如说,现在有一个线程A和一个线程B读取hashMap数据进行数组扩容

  1. A和B中数组里数据的一个原链表是A → B
  2. 本来线程1先扩容, 但是线程2抢先完成扩容,用头插法把链表变成B → A——即B.next = A, A = null
  3. 线程1继续迁移时,它先处理A,把A放入新链表
  4. 然后处理B,发现B.next指向了A(被线程2改的)
  5. 把B插入链表头部后,B.next还是指向A
  6. 然后线程1又去处理A,A的next指向B
  7. 形成循环引用A ↔ B,程序卡死

当然,JDK 8 将扩容算法做了调整,不再将元素加入链表头(而是保持与扩容前一样的顺序),尾插法,就避免了jdk7中死循环的问题。

4.8面试题-HashSet与HashMap的区别
#我的表述 从底层数据结构看 hashSet是集合 hashMap是数组+链表/数组+红黑树 从数据效率看 hashMap的查找效率是O(1), 而HashSete则是O(n) HashMap和HashSet的插入删除在有数组下标索引时的时间复杂度都是O(1), 如果索引未知, 则时间复杂度都是O(n) 从内存大小看 HashSet存储的是不重复的元素, 而HashMap则是键值对, 会出现采用hashCode的hash值计算数组下标索引, 而数组下标索引相同但key不同, 会插入到链表/红黑树中, 所以会出现更多的数据 从线程安全看 HashSet和HashMap都是非线程安全 --上述表述错误

(1)HashSet实现了Set接口, 仅存储对象; HashMap实现了Map接口, 存储的是键值对.

(2)HashSet底层其实是用HashMap实现存储的, HashSet封装了一系列HashMap的方法. 依靠HashMap来存储元素值,(利用hashMap的key键进行存储), 而value值默认为Object对象. 所以HashSet也不允许出现重复值, (后面这句话理解麻烦, 用AI进行业务场景描述), 判断标准和HashMap判断标准相同, 两个元素的hashCode相等并且通过equals()方法返回true.

4.9面试题-HashTable与HashMap的区别

难易程度:☆☆

出现频率:☆☆

#我的表述 HashTable实现了Array接口, 存储的是数组, HashMap实现了Map接口, 存储键值对 HashTable的底层是HashMap, HashTable封装了一系列HashMap方法, 用HashMap进行存储, 其hashMap的key键存储, 用value值存储元素值, 所以hashTable存储不允许出现重复值. -- 上述我的表述错误

主要区别:

区别HashTableHashMap
数据结构数组+链表数组+链表+红黑树
是否可以为nullKey和value都不能为null可以为null
hash算法HashTable直接用key.hashCode()取模HashMap先高16位和低16位异或,再用位运算,分布更均匀
扩容方式当前容量翻倍 +1当前容量翻倍
线程安全同步(synchronized)的,线程安全非线程安全

在实际开发中不建议使用HashTable,在多线程环境下可以使用ConcurrentHashMap类

实际场景上面表格表述 = 如下表述:

第一, 数据结构, HashTable是数组 + 链表, 而HashMap是数组 + 链表 / 数组 + 红黑树

第二, 是否可以为null, HashTable的key和value不可以为null, 而HashMap的键值对可以为null

第三, Hash算法, HashTable是用hashCode计算, 再取模, 而HashMap是高16位与低16位异或, 然后再位运算, 使哈希分布更均匀.

第四, 数组扩容, HashTable是原先翻倍+1, 而HashMap是原先翻倍

第五, 线程安全, HashTable是同步synchronized, 是线程安全的, 而HashMap是非线程安全的.

所以在实际开发中不建议使用HashTable, 而在多线程环境下使用ConcurrentHashMap.

http://www.gsyq.cn/news/1429115.html

相关文章:

  • Vue——别再自己写枚举了!RuoYi-Vue3字典管理全局缓存,让代码量减少90%
  • 终极压缩包密码找回指南:如何用ArchivePasswordTestTool轻松破解加密文件
  • 2026年 风机/上风风机/上风通风机/边墙风机厂家推荐榜:技术实力与通风性能深度盘点 - 品牌企业推荐师(官方)
  • 如何在Windows上轻松搞定PDF处理:Poppler终极指南
  • 现在不评估Gemini替代方案,Q4可能面临API配额冻结风险:2024下半年Google Cloud政策突变预警
  • 如何用Universal Pokemon Randomizer ZX为宝可梦游戏注入无限新鲜感?
  • Apache Airflow:彻底解决复杂工作流调度难题的数据管道自动化平台
  • GEO公司集中在哪里?
  • 3个实战场景:如何用Smart Money Concepts构建机构级交易策略
  • C++ -- 堆栈的分配和大小端
  • Gemini商业分析报告效能评估白皮书(2024Q2独家数据+ROI测算模型)
  • 暗黑破坏神2存档编辑器:免费Web版工具完全指南
  • C# SQLite参数化查询实战:防SQL注入与数据访问层封装
  • Firmware Extractor:安卓固件逆向工程的一体化解决方案
  • Android View 绘制流程 与invalidate 和postInvalidate 分析--从源码角度
  • 不只是编译:用BES SDK和GCC-Arm工具链,在Windows上打造你的第一个蓝牙音频固件
  • 基于Arduino与TEA5767的FM收音机制作:从原理到实践的完整指南
  • 第25篇|Surface 预览控制:ArkUI 页面如何接住相机画面
  • APP攻防-资产收集篇反代理反证书反模拟器MsgiskLSP模块系统证书
  • 猫抓Cat-Catch:浏览器视频下载神器,一键嗅探网页媒体资源完整指南
  • 解锁小说离线阅读新可能:novel-downloader重新定义数字阅读体验
  • 如何用SMUDebugTool解锁AMD Ryzen处理器的终极性能:完全指南
  • 别再死记硬背了!用Kettle+MySQL手把手还原一个‘客户忠诚度分级’复杂存储过程
  • COM3D2.MaidFiddler:如何用实时编辑器快速修改COM3D2女仆属性
  • 横向辅助驾驶及人机共驾控制策略优化【附仿真】
  • 终极指南:使用msoffcrypto-tool轻松解锁加密Office文档
  • 5分钟搞定200+小说网站:novel-downloader离线阅读终极指南
  • 5步实现加密音频格式转换:开源工具深度解析与应用指南
  • UniApp + Painter实战:从‘社交裂变’到‘数据报告’,解锁小程序图片生成的3个高级应用场景
  • HS2-HF Patch终极指南:如何轻松优化你的Honey Select 2游戏体验