基础编程学习快乐每一天
首页
留言
Siddim.com
当前位置:
首页
>
编程知识库
>
后端开发知识
>
Java容器面试题:谈谈你对 HashMap 的理解
Java容器面试题:谈谈你对 HashMap 的理解
阅读
1
2020-03-25
为了能够在面试回答中优雅而不失体面回答面试考点,该文章借鉴了不同平台对知识点的描述。
回答
HashMap
是一种存取高效但不保证有序的常用容器。它的数据结构为“数组 链表”,是解决哈希冲突的产物,也就是我们常说的链地址法。它实现了
Map
接口采用
K
-
V
键值对存储数据,并实现了浅拷贝和序列化。
HashMap
的默认初始大小为
16
,初始化大小必须为
2
的幂,最大大小为
2
的
30
次方。数组中存储的链表节点
Entry
类实现于
Map
.
Entry
接口,它实现了对节点的通用操作。
HashMap
的阈值默认为“容量*
0
.
75f
”,当存储节点数量超过该值,则对
map
进行扩容处理。
HashMap
提供了
4
种构造方法,分别是默认构造方法;可以指定初始容量的构造方法;可以指定初始容量和阈值的构造方法以及基于一个
Map
的构造方法。虽然是构造函数,但是真正的初始化都是在第一次添加操作里面实现的。
在第一次添加操作中,
HashMap
会先判断存储数组有没有初始化,如果没有先进行初始化操作,初始化过程中会取比用户指定的容量大的最近的
2
的幂次方数作为数组的初始容量,并更新扩容的阈值。
接着添加操作讲解。添加操作的执行流程为:
先判断有没有初始化
再判断传入的
key
是否为空,为空保存在
table
[
o
] 位置
key
不为空就对
key
进
hash
,
hash
的结果再&
amp
; 数组的长度就得到存储的位置
如果存储位置为空则创建节点,不为空就说明存在冲突
解决冲突
HashMap
会先遍历链表,如果有相同的
value
就更新旧值,否则构建节点添加到链表头
添加还要先判断存储的节点数量是否达到阈值,到达阈值要进行扩容
扩容扩
2
倍,是新建数组所以要先转移节点,转移时都重新计算存储位置,可能保持不变可能为旧容量 位置。
扩容结束后新插入的元素也得再
hash
一遍才能插入。
获取节点的操作和添加差不多,也是
先判断是否为空,为空就在
table
[
0
] 去找值
不为空也是先
hash
,&
amp
;数组长度计算下标位置
再遍历找相同的
key
返回值
HashMap
的其他操作大同小异,再讲讲
HashMap1
.
7
的问题还有
1
.
7
和
1
.
8
的差别。
HashMap
是一个并发不安全的容器,在迭代操作是采用的是
fast
-
fail
机制;在并发添加操作中会出现丢失更新的问题;因为采用头插法在并发扩容时会产生环形链表的问题,导致
CPU
到达
100
%,甚至宕机。
解决并发问题可以采用
Java
类库提供的
Collections
工具包下的
Collections
.
synchronizedMap
()方法,返回一个线程安全的
Map
或者使用并发包下的
ConcurrentHashMap
,
ConcurrentHashMap
采用分段锁机制实现线程安全
使用
HashTable
(不推荐)
Hash1
.
7
和
1
.
8
最大的不同在于
1
.
8
采用了“数组 链表 红黑树”的数据结构,在链表长度超过
8
时,把链表转化成红黑树来解决
HashMap
因链表变长而查询变慢的问题;其次
在
hash
取下标时将
1
.
7
的
9
次扰动(
5
次按位与和
4
次位运算)改为
2
次(一次按位与和一次位运算)
1
.
7
的底层节点为
Entry
,
1
.
8
为
node
,但是本质一样,都是
Map
.
Entry
的实现
还有就是在存取数据时添加了关于树结构的遍历更新与添加操作,并采用了尾插法来避免环形链表的产生
但是并发丢失更新的问题依然存在。
回答顺序:数据结构 继承结构 基本字段 构造方法 添加操作 扩容操作 获取操作 并发问题 与
1
.
8
的区别
考点分析
HashMap
作为最基本的容器,它本身的设计与
1
.
7
1
.
8
的差异性导致
HashMap
成为面试中最最高频的考点。所以掌握
HashMap
势在必行,但是想要在各种宽泛的回答中脱颖而出,就必须对
hashMap
前因后果了然于胸。
考点一:为什么初始容量必须为2 的幂?为什么负载因子为0.75f?为什么要做那么多扰动处理?
这些问题都要围绕一个点来回答:减少哈希冲突。
(
1
)容量必须为
2
的幂是为了增加取值的可能性。
2
的
n
次幂转化为二进制为
1
后面
n
个
0
,在计算下标的时候是
hash
&
amp
;(
length
-
1
),也就是&
amp
;(
n
-
1
)个
1
:初始容量为
4
-&
gt
;
100
,
length
-
1
-&
gt
;
11
。所有的二进制为都为
1
有什么好处?
0
/
1
&
amp
;
1
都为它本身
0
/
1
&
amp
;
0
都为
0
可以看出&
amp
;
1
保证了取值的平均。如果某一位为
0
,比如最后一位,那么它&
amp
;出来下标就一定是个偶数,减少了
HashMap
数组一半的取值,大大增加了冲突的可能。
(
2
)负载因子为
0
.
75f
是空间与时间的均衡
如果负载因子小,意味着阈值变小。比如容量为
10
的
HashMap
,负载因子为
0
.
5f
,那么存储
5
个就会扩容到
20
,出现哈希冲突的可能性变小,但是空间利用率不高。适用于有足够内存并要求查询效率的场景。
相反如果阈值为
1
,那么容量为
10
,就必须存储
10
个元素才进行扩容,出现冲突的概率变大,极端情况下可能会从
O
(
1
)退化到
O
(
n
)。适用于内存敏感但不要求要求查询效率的场景
(
3
)
hash
() 的意义在于使
hash
结果不同
hash
算法的好坏直接影响
hash
结构的效率,坏的
hash
算法极端情况下可能会使
hash
结构的存取效率从
O
(
1
)退化到
O
(
n
)。
1
.
8
之所以把
9
次扰动降到
2
次,是出于计算效率的考虑。
考点二:& 字符虽然和 % 效果一样,但是操作效率更高
考点三:为什么int,String 适合最为key?
int
和
String
的好处在于
hash
出来的值不会改变。如果是一个对象,那么他们可能会因为内部引用的改变而
hashCode
值的改变,会导致存储重复的数据或找不到数据的情况。
考点四:并发操作导致的添加丢失和环形链表的产生过程
知识点拓展
不仅仅是
HashMap
的东西,根据你的回答,面试官会引出很多其他的问题,所以你在自己设计回答的过程中可以有意识引导面试官问出你熟悉的内容,安排的明明白白。
拓展一:解决Hash 冲突的不同方案
链地址法
开发地址:线性探测法、平方探测法
完全散列:布谷鸟散列
拓展二:HashMap 是浅拷贝,说一说浅拷贝和深拷贝的区别
拓展三:说一说Collections.synchronizedMap()和HashTable 的区别
拓展四:说一说HashMap 如何实现有序(LinkHashMap 和TreeMap)以及他们的差别
拓展五:说一说ConcurrentHashMap 如何实现线程安全
结尾
这篇文章更多的是
HashMap
面试怎么答,以及需要注意的知识点,希望对你有所帮助。
来源:
juejin
.
im
/
post
/
5c1da988f265da6143130ccc
最近五期
【
61
期】
MySQL
行锁和表锁的含义及区别(
MySQL
面试第四弹)
【
62
期】解释一下
MySQL
中内连接,外连接等的区别(
MySQL
面试第五弹)
【
63
期】谈谈
MySQL
索引,
B
树原理,以及建索引的几大原则(
MySQL
面试第六弹)
【
64
期】
MySQL
服务占用
cpu
100
%,如何排查问题? (
MySQL
面试第七弹)
【
65
期】
Spring
的
IOC
是啥?有什么好处?
? ~
以上数据来源于网络,如有侵权,请联系删除。
上一篇:
Spring的IOC是啥?有什么好处?
下一篇:
谈谈ConcurrentHashMap是如何保证线程安全的?
评论
(0)
提交
类别
基础编程学习
HTML
PHP
Python
编程知识库
后端开发知识
热门文章
Java并发中的同步容器与并发容器,你了解多少?
Innodb中的事务隔离级别和锁的关系,难倒一半面试者!
SpringBoot + minio实现分片上传、秒传、续传
面试官:你知道消息队列如何保证数据不丢失吗?
JAVA知识 Java8新特性
面试官:谈谈为什么要限流,有哪些限流方案?
说说动态代理与静态代理区别
面试官:思考Tomcat 类加载器为什么要违背双亲委派模型?
boot-admin 基于SpringBoot的后台权限管理系统,可作为脚手架,用于快速搭建项目
SpringBoot+Vue+App+硬件实现智能家居系统项目