登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
AI 队友
登录
注册
Gitee 年度开源项目评选中~
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
Watch
不关注
关注所有动态
仅关注版本发行动态
关注但不提醒动态
3
Star
45
Fork
21
DreamCoders
/
CoderGuide
代码
Issues
1169
Pull Requests
0
Wiki
统计
流水线
服务
JavaDoc
PHPDoc
质量分析
Jenkins for Gitee
腾讯云托管
腾讯云 Serverless
悬镜安全
阿里云 SAE
Codeblitz
SBOM
我知道了,不再自动展开
更新失败,请稍后重试!
移除标识
内容风险标识
本任务被
标识为内容中包含有代码安全 Bug 、隐私泄露等敏感信息,仓库外成员不可访问
谈谈你对 HashMap 的理解
待办的
#IAJKRX
陌生人
拥有者
创建于
2024-08-13 10:05
<p style="text-align: start;">HashMap 是一种存取高效但不保证有序的常用容器。它的数据结构为“数组+链表”,是解决哈希冲突的产物,也就是我们常说的链地址法。它实现了Map 接口采用K-V 键值对存储数据,并实现了浅拷贝和序列化。</p><p style="text-align: start;">HashMap 的默认初始大小为16,初始化大小必须为2的幂,最大大小为2的30次方。数组中存储的链表节点Entry 类实现于Map.Entry 接口,它实现了对节点的通用操作。</p><p style="text-align: start;">HashMap 的阈值默认为“容量*0.75f”,当存储节点数量超过该值,则对map 进行扩容处理。</p><p style="text-align: start;">HashMap 提供了4种构造方法,分别是<strong>默认构造</strong>方法;可以<strong>指定初始容量的构造</strong>方法;可以<strong>指定初始容量和阈值的构造</strong>方法以及<strong>基于一个Map 的构造</strong>方法。虽然是构造函数,但是真正的初始化都是在第一次添加操作里面实现的。</p><p style="text-align: start;">在第一次添加操作中,HashMap 会先判断存储数组有没有初始化,如果没有先进行初始化操作,初始化过程中会取比用户指定的容量大的最近的2 的幂次方数作为数组的初始容量,并更新扩容的阈值。</p><p style="text-align: start;"><span style="color: inherit;"><strong>接着添加操作讲解。添加操作的执行流程为:</strong></span></p><ul><li style="text-align: start;">先判断有没有初始化</li><li style="text-align: start;">再判断传入的key 是否为空,为空保存在table[o] 位置</li><li style="text-align: start;">key 不为空就对key 进hash,hash 的结果再& 数组的长度就得到存储的位置</li><li style="text-align: start;">如果存储位置为空则创建节点,不为空就说明存在冲突</li><li style="text-align: start;">解决冲突HashMap 会先遍历链表,如果有相同的value 就更新旧值,否则构建节点添加到链表头</li><li style="text-align: start;">添加还要先判断存储的节点数量是否达到阈值,到达阈值要进行扩容</li><li style="text-align: start;">扩容扩2倍,是新建数组所以要先转移节点,转移时都重新计算存储位置,可能保持不变可能为旧容量+位置。</li><li style="text-align: start;">扩容结束后新插入的元素也得再hash 一遍才能插入。</li></ul><p style="text-align: start;">获取节点的操作和添加差不多,也是</p><ul><li style="text-align: start;">先判断是否为空,为空就在table[0] 去找值</li><li style="text-align: start;">不为空也是先hash,&数组长度计算下标位置</li><li style="text-align: start;">再遍历找相同的key 返回值</li></ul><p style="text-align: start;"><span style="color: inherit;"><strong>HashMap 的其他操作大同小异,再讲讲HashMap1.7 的问题还有1.7 和1.8 的差别。</strong></span></p><p style="text-align: start;">HashMap 是一个并发不安全的容器,在迭代操作是采用的是fast-fail 机制;在并发添加操作中会出现丢失更新的问题;因为采用头插法在并发扩容时会产生环形链表的问题,导致CPU 到达100%,甚至宕机。</p><p style="text-align: start;">解决并发问题可以采用</p><ul><li style="text-align: start;">Java 类库提供的Collections 工具包下的Collections.synchronizedMap()方法,返回一个线程安全的Map</li><li style="text-align: start;">或者使用并发包下的 ConcurrentHashMap,ConcurrentHashMap采用分段锁机制实现线程安全</li><li style="text-align: start;">使用HashTable (不推荐)</li></ul><p style="text-align: start;">Hash1.7 和1.8 最大的不同在于1.8 采用了“数组+链表+红黑树”的数据结构,在链表长度超过8 时,把链表转化成红黑树来解决HashMap 因链表变长而查询变慢的问题;其次</p><ul><li style="text-align: start;">在hash 取下标时将1.7 的9次扰动(5次按位与和4次位运算)改为2次(一次按位与和一次位运算)</li><li style="text-align: start;">1.7 的底层节点为Entry,1.8 为node ,但是本质一样,都是Map.Entry 的实现</li><li style="text-align: start;">还有就是在存取数据时添加了关于树结构的遍历更新与添加操作,并采用了尾插法来避免环形链表的产生</li><li style="text-align: start;">但是并发丢失更新的问题依然存在。</li></ul><p style="text-align: start;"><span style="color: rgb(217, 33, 66);"><strong>回答顺序:数据结构+继承结构+基本字段+构造方法+添加操作+扩容操作+获取操作+并发问题+与1.8的区别</strong></span></p><h2 style="text-align: start;"><span style="color: inherit;">考点分析</span></h2><p style="text-align: start;">HashMap 作为最基本的容器,它本身的设计与1.7 1.8的差异性导致HashMap 成为面试中最最高频的考点。所以掌握HashMap 势在必行,但是想要在各种宽泛的回答中脱颖而出,就必须对hashMap 前因后果了然于胸。</p><h3 style="text-align: start;"><span style="color: inherit;">考点一:为什么初始容量必须为2 的幂?为什么负载因子为0.75f?为什么要做那么多扰动处理?</span></h3><p style="text-align: start;">这些问题都要围绕一个点来回答:<strong>减少哈希冲突。</strong></p><p style="text-align: start;"><span style="color: rgb(171, 25, 66);"><strong>(1)容量必须为2 的幂是为了增加取值的可能性。</strong></span></p><p style="text-align: start;">2 的n次幂转化为二进制为1后面n个0,在计算下标的时候是hash&(length - 1),也就是&(n-1)个1:初始容量为4->100,length-1 -> 11。所有的二进制为都为1有什么好处?</p><ul><li style="text-align: start;">0/1 & 1 都为它本身</li><li style="text-align: start;">0/1 & 0 都为 0</li></ul><p style="text-align: start;">可以看出&1保证了取值的平均。如果某一位为0 ,比如最后一位,那么它&出来下标就一定是个偶数,减少了HashMap 数组一半的取值,大大增加了冲突的可能。</p><p style="text-align: start;"><span style="color: rgb(171, 25, 66);"><strong>(2)负载因子为0.75f 是空间与时间的均衡</strong></span></p><p style="text-align: start;">如果负载因子小,意味着阈值变小。比如容量为10 的HashMap,负载因子为0.5f,那么存储5个就会扩容到20,出现哈希冲突的可能性变小,但是空间利用率不高。适用于有足够内存并要求查询效率的场景。</p><p style="text-align: start;">相反如果阈值为1 ,那么容量为10,就必须存储10个元素才进行扩容,出现冲突的概率变大,极端情况下可能会从O(1)退化到O(n)。适用于内存敏感但不要求要求查询效率的场景</p><p style="text-align: start;"><span style="color: rgb(171, 25, 66);"><strong>(3)hash() 的意义在于使hash 结果不同</strong></span></p><p style="text-align: start;">hash 算法的好坏直接影响hash 结构的效率,坏的hash 算法极端情况下可能会使hash 结构的存取效率从O(1)退化到O(n)。1.8 之所以把9 次扰动降到2 次,是出于计算效率的考虑。</p><h3 style="text-align: start;"><span style="color: inherit;">考点二:& 字符虽然和 % 效果一样,但是操作效率更高</span></h3><h3 style="text-align: start;"><span style="color: inherit;">考点三:为什么int,String 适合最为key?</span></h3><p style="text-align: start;">int 和 String 的好处在于hash 出来的值不会改变。如果是一个对象,那么他们可能会因为内部引用的改变而hashCode 值的改变,会导致存储重复的数据或找不到数据的情况。</p><h3 style="text-align: start;"><span style="color: inherit;">考点四:并发操作导致的添加丢失和环形链表的产生过程</span></h3><h2 style="text-align: start;"><span style="color: inherit;">知识点拓展</span></h2><p style="text-align: start;">不仅仅是HashMap 的东西,根据你的回答,面试官会引出很多其他的问题,所以你在自己设计回答的过程中可以有意识引导面试官问出你熟悉的内容,安排的明明白白。</p><h4 style="text-align: start;"><span style="color: inherit;">拓展一:解决Hash 冲突的不同方案</span></h4><ul><li style="text-align: start;">链地址法</li><li style="text-align: start;">开发地址:线性探测法、平方探测法</li><li style="text-align: start;">完全散列:布谷鸟散列</li></ul><h4 style="text-align: start;"><span style="color: inherit;">拓展二:HashMap 是浅拷贝,说一说浅拷贝和深拷贝的区别</span></h4><h4 style="text-align: start;"><span style="color: inherit;">拓展三:说一说Collections.synchronizedMap()和HashTable 的区别</span></h4><h4 style="text-align: start;"><span style="color: inherit;">拓展四:说一说HashMap 如何实现有序(LinkHashMap 和TreeMap)以及他们的差别</span></h4><h4 style="text-align: start;"><span style="color: inherit;">拓展五:说一说ConcurrentHashMap 如何实现线程安全</span></h4><h2 style="text-align: start;"><span style="color: inherit;">结尾</span></h2><p style="text-align: start;">这篇文章更多的是HashMap 面试怎么答,以及需要注意的知识点,希望对你有所帮助。</p>
<p style="text-align: start;">HashMap 是一种存取高效但不保证有序的常用容器。它的数据结构为“数组+链表”,是解决哈希冲突的产物,也就是我们常说的链地址法。它实现了Map 接口采用K-V 键值对存储数据,并实现了浅拷贝和序列化。</p><p style="text-align: start;">HashMap 的默认初始大小为16,初始化大小必须为2的幂,最大大小为2的30次方。数组中存储的链表节点Entry 类实现于Map.Entry 接口,它实现了对节点的通用操作。</p><p style="text-align: start;">HashMap 的阈值默认为“容量*0.75f”,当存储节点数量超过该值,则对map 进行扩容处理。</p><p style="text-align: start;">HashMap 提供了4种构造方法,分别是<strong>默认构造</strong>方法;可以<strong>指定初始容量的构造</strong>方法;可以<strong>指定初始容量和阈值的构造</strong>方法以及<strong>基于一个Map 的构造</strong>方法。虽然是构造函数,但是真正的初始化都是在第一次添加操作里面实现的。</p><p style="text-align: start;">在第一次添加操作中,HashMap 会先判断存储数组有没有初始化,如果没有先进行初始化操作,初始化过程中会取比用户指定的容量大的最近的2 的幂次方数作为数组的初始容量,并更新扩容的阈值。</p><p style="text-align: start;"><span style="color: inherit;"><strong>接着添加操作讲解。添加操作的执行流程为:</strong></span></p><ul><li style="text-align: start;">先判断有没有初始化</li><li style="text-align: start;">再判断传入的key 是否为空,为空保存在table[o] 位置</li><li style="text-align: start;">key 不为空就对key 进hash,hash 的结果再& 数组的长度就得到存储的位置</li><li style="text-align: start;">如果存储位置为空则创建节点,不为空就说明存在冲突</li><li style="text-align: start;">解决冲突HashMap 会先遍历链表,如果有相同的value 就更新旧值,否则构建节点添加到链表头</li><li style="text-align: start;">添加还要先判断存储的节点数量是否达到阈值,到达阈值要进行扩容</li><li style="text-align: start;">扩容扩2倍,是新建数组所以要先转移节点,转移时都重新计算存储位置,可能保持不变可能为旧容量+位置。</li><li style="text-align: start;">扩容结束后新插入的元素也得再hash 一遍才能插入。</li></ul><p style="text-align: start;">获取节点的操作和添加差不多,也是</p><ul><li style="text-align: start;">先判断是否为空,为空就在table[0] 去找值</li><li style="text-align: start;">不为空也是先hash,&数组长度计算下标位置</li><li style="text-align: start;">再遍历找相同的key 返回值</li></ul><p style="text-align: start;"><span style="color: inherit;"><strong>HashMap 的其他操作大同小异,再讲讲HashMap1.7 的问题还有1.7 和1.8 的差别。</strong></span></p><p style="text-align: start;">HashMap 是一个并发不安全的容器,在迭代操作是采用的是fast-fail 机制;在并发添加操作中会出现丢失更新的问题;因为采用头插法在并发扩容时会产生环形链表的问题,导致CPU 到达100%,甚至宕机。</p><p style="text-align: start;">解决并发问题可以采用</p><ul><li style="text-align: start;">Java 类库提供的Collections 工具包下的Collections.synchronizedMap()方法,返回一个线程安全的Map</li><li style="text-align: start;">或者使用并发包下的 ConcurrentHashMap,ConcurrentHashMap采用分段锁机制实现线程安全</li><li style="text-align: start;">使用HashTable (不推荐)</li></ul><p style="text-align: start;">Hash1.7 和1.8 最大的不同在于1.8 采用了“数组+链表+红黑树”的数据结构,在链表长度超过8 时,把链表转化成红黑树来解决HashMap 因链表变长而查询变慢的问题;其次</p><ul><li style="text-align: start;">在hash 取下标时将1.7 的9次扰动(5次按位与和4次位运算)改为2次(一次按位与和一次位运算)</li><li style="text-align: start;">1.7 的底层节点为Entry,1.8 为node ,但是本质一样,都是Map.Entry 的实现</li><li style="text-align: start;">还有就是在存取数据时添加了关于树结构的遍历更新与添加操作,并采用了尾插法来避免环形链表的产生</li><li style="text-align: start;">但是并发丢失更新的问题依然存在。</li></ul><p style="text-align: start;"><span style="color: rgb(217, 33, 66);"><strong>回答顺序:数据结构+继承结构+基本字段+构造方法+添加操作+扩容操作+获取操作+并发问题+与1.8的区别</strong></span></p><h2 style="text-align: start;"><span style="color: inherit;">考点分析</span></h2><p style="text-align: start;">HashMap 作为最基本的容器,它本身的设计与1.7 1.8的差异性导致HashMap 成为面试中最最高频的考点。所以掌握HashMap 势在必行,但是想要在各种宽泛的回答中脱颖而出,就必须对hashMap 前因后果了然于胸。</p><h3 style="text-align: start;"><span style="color: inherit;">考点一:为什么初始容量必须为2 的幂?为什么负载因子为0.75f?为什么要做那么多扰动处理?</span></h3><p style="text-align: start;">这些问题都要围绕一个点来回答:<strong>减少哈希冲突。</strong></p><p style="text-align: start;"><span style="color: rgb(171, 25, 66);"><strong>(1)容量必须为2 的幂是为了增加取值的可能性。</strong></span></p><p style="text-align: start;">2 的n次幂转化为二进制为1后面n个0,在计算下标的时候是hash&(length - 1),也就是&(n-1)个1:初始容量为4->100,length-1 -> 11。所有的二进制为都为1有什么好处?</p><ul><li style="text-align: start;">0/1 & 1 都为它本身</li><li style="text-align: start;">0/1 & 0 都为 0</li></ul><p style="text-align: start;">可以看出&1保证了取值的平均。如果某一位为0 ,比如最后一位,那么它&出来下标就一定是个偶数,减少了HashMap 数组一半的取值,大大增加了冲突的可能。</p><p style="text-align: start;"><span style="color: rgb(171, 25, 66);"><strong>(2)负载因子为0.75f 是空间与时间的均衡</strong></span></p><p style="text-align: start;">如果负载因子小,意味着阈值变小。比如容量为10 的HashMap,负载因子为0.5f,那么存储5个就会扩容到20,出现哈希冲突的可能性变小,但是空间利用率不高。适用于有足够内存并要求查询效率的场景。</p><p style="text-align: start;">相反如果阈值为1 ,那么容量为10,就必须存储10个元素才进行扩容,出现冲突的概率变大,极端情况下可能会从O(1)退化到O(n)。适用于内存敏感但不要求要求查询效率的场景</p><p style="text-align: start;"><span style="color: rgb(171, 25, 66);"><strong>(3)hash() 的意义在于使hash 结果不同</strong></span></p><p style="text-align: start;">hash 算法的好坏直接影响hash 结构的效率,坏的hash 算法极端情况下可能会使hash 结构的存取效率从O(1)退化到O(n)。1.8 之所以把9 次扰动降到2 次,是出于计算效率的考虑。</p><h3 style="text-align: start;"><span style="color: inherit;">考点二:& 字符虽然和 % 效果一样,但是操作效率更高</span></h3><h3 style="text-align: start;"><span style="color: inherit;">考点三:为什么int,String 适合最为key?</span></h3><p style="text-align: start;">int 和 String 的好处在于hash 出来的值不会改变。如果是一个对象,那么他们可能会因为内部引用的改变而hashCode 值的改变,会导致存储重复的数据或找不到数据的情况。</p><h3 style="text-align: start;"><span style="color: inherit;">考点四:并发操作导致的添加丢失和环形链表的产生过程</span></h3><h2 style="text-align: start;"><span style="color: inherit;">知识点拓展</span></h2><p style="text-align: start;">不仅仅是HashMap 的东西,根据你的回答,面试官会引出很多其他的问题,所以你在自己设计回答的过程中可以有意识引导面试官问出你熟悉的内容,安排的明明白白。</p><h4 style="text-align: start;"><span style="color: inherit;">拓展一:解决Hash 冲突的不同方案</span></h4><ul><li style="text-align: start;">链地址法</li><li style="text-align: start;">开发地址:线性探测法、平方探测法</li><li style="text-align: start;">完全散列:布谷鸟散列</li></ul><h4 style="text-align: start;"><span style="color: inherit;">拓展二:HashMap 是浅拷贝,说一说浅拷贝和深拷贝的区别</span></h4><h4 style="text-align: start;"><span style="color: inherit;">拓展三:说一说Collections.synchronizedMap()和HashTable 的区别</span></h4><h4 style="text-align: start;"><span style="color: inherit;">拓展四:说一说HashMap 如何实现有序(LinkHashMap 和TreeMap)以及他们的差别</span></h4><h4 style="text-align: start;"><span style="color: inherit;">拓展五:说一说ConcurrentHashMap 如何实现线程安全</span></h4><h2 style="text-align: start;"><span style="color: inherit;">结尾</span></h2><p style="text-align: start;">这篇文章更多的是HashMap 面试怎么答,以及需要注意的知识点,希望对你有所帮助。</p>
评论 (
0
)
登录
后才可以发表评论
状态
待办的
待办的
进行中
已完成
已关闭
负责人
未设置
标签
Java
未设置
标签管理
里程碑
未关联里程碑
未关联里程碑
Pull Requests
未关联
未关联
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
未关联
未关联
master
开始日期   -   截止日期
-
置顶选项
不置顶
置顶等级:高
置顶等级:中
置顶等级:低
优先级
不指定
严重
主要
次要
不重要
参与者(1)
1
https://gitee.com/DreamCoders/CoderGuide.git
git@gitee.com:DreamCoders/CoderGuide.git
DreamCoders
CoderGuide
CoderGuide
点此查找更多帮助
搜索帮助
Git 命令在线学习
如何在 Gitee 导入 GitHub 仓库
Git 仓库基础操作
企业版和社区版功能对比
SSH 公钥设置
如何处理代码冲突
仓库体积过大,如何减小?
如何找回被删除的仓库数据
Gitee 产品配额说明
GitHub仓库快速导入Gitee及同步更新
什么是 Release(发行版)
将 PHP 项目自动发布到 packagist.org
评论
仓库举报
回到顶部
登录提示
该操作需登录 Gitee 帐号,请先登录后再操作。
立即登录
没有帐号,去注册