为什么使用HashMap需要重写hashcode和equals方法_java常见面试题敲黑板了,HashMap最全的整理,大厂必考...
最近几天,在这样的大环境下显得疲惫不堪,但是我还是写下了这篇文章,希望对任何人都有用。
HashMap是我们经常用到的数据结构,由数组和链表组成的数据结构如下图所示
上方是一张数组图片,数组里面每个地方都存了Key-Value这样的实例,值得注意的是在java7版本中叫Entry,而在Java8里面叫做Node(节点)。因为初始化的时候所有的位置都是Null,在执行插入操作的时候会根据hash算法把key作为健得出一个Index的值。
好比如我put(“老婆”,114)的操作,则我插入了“老婆”这个元素,这个时候会把"老婆"作为hash函数的参数,计算出插入在数组的那个位置,如果根据哈希算法计算出index=2那结果如下图所示
hash(“老婆”)=2
面试官:你刚才提到了链表,为啥有数组还需要链表,链表又是什么?
我们都知道数组的长度是有限的,在有限的数组长度内我们使用哈希,哈希本身就有概率性,当执行put("l老婆",114)和put("老公",220)这就计算了两个哈希,有几率计算出的哈希值会一样,就像上面的一样我再次哈希put("老公",220)极端情况下也会哈希到一个值上,那就只能用链表表达。
如下图。
每一个节点(Node)都会保存自身的hash,key,value,以及下一个节点,这是Node的源码
数组存储的是Node,next代表下一个hash相同的元素,就形成了一个想链条一样的永远都不会断。
面试官:既然说到了链表,那你说下新的Enter节点在插入的时候,是怎么插入的呢
java8之前插入的顺序是往头部插入,就是说新来的元素会取代原来的元素,原来旧的元素就顺便推到链表中去,也就是说新元素next指向的是旧的元素。但是在java8之后,都是所有的尾部插入了。
面试官:我很好奇为啥改为尾部插入了呢?
这个问题!!!还好我饱读诗书,看我细细道来!!
有些人认为是java作者的随机性而为,没有杀luan用,其实暗藏玄机,首先我们看下HashMap的扩容机制:
我提到了数组的容量是有限的,数据经过多次插入,到达一定数量就会用完,所以会进行自动扩容,也就是resize的操作。
面试官:什么时候会触发resize扩容的操作?
扩容有两个因素影响:
- Capocity:HashMap当前长度。
- LoadFactor:负载因子,默认值0.75f
如何理解?例如,当前容量为100,让你存入76个元素的时候,不会立马执行存入操作,而是每次插插入的时候都是判断是否需要进行resize,如果是那就扩容,但是HashMap也不是那么简单的阔大点容量就行了。
面试官:MashMap遇到需要扩容是是怎么扩容的?
- 分两步走:
- 扩容:创建一个新的Entry空数组,长度是原数组的2倍
ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组
面试官:为什么要重新Hash呢,直接复制过去不香么?
卧槽这个问题提得好!有点知识盲区呀!
1x1得 1 1x2 得 2 …. 有了,我想起来敖丙那天晚上在我耳边的话了:假如我年少有为不自卑,懂得什么是珍贵,那些美梦没给你,我一生有愧….什么鬼!
面试官:是因为长度扩大以后,Hash的规则也随之改变。
Hash的公式---> index = HashCode(Key) & (Length - 1)
原来长度(Length)是8你位运算出来的值是2 ,新的长度是16你位运算出来的值明显不一样了。
扩容前:
扩容后:
面试官:继续问题,为啥之前用头部插法,java8之后改用尾部插法了呢?
我就先举个例子吧,现在往一个容量大小为2的put两个值,负载因子是0.75是不是我们在put第二个的时候就会进行resize?
2*0.75 = 1 所以插入第二个就要resize了
现在我们要在容量为2的容器里面用不同线程插入A,B,C,假如我们在resize之前打个短点,那意味着数据都插入了但是还没resize那扩容前可能是这样的。
我们可以看到链表的指向变成了这样的画风,元素A-〉B-〉C,
A的下一个指针指向B,B再指向C,
突然就变得奇怪了!!!
因为resize的赋值方式,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置,在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。
可能存在以下情况。你觉得有什么问题吗?
B指向了A
一旦几个线程都调整完毕,就可能出现环形链表,这个时候如果发生取值的操作悲剧就来了。
环形链表 B-〉A-〉B
面试官:不错,看来你理解的差不了。那java8的尾部插入是怎么回事?
因为在java8版本的HashMap中引入了红黑树,红黑树的引入大大的提高了HashMap的性能,原本只是O(n)的时间复杂度降低到了O(logn).
关于红黑树我在其他的专栏有专门的讲过,本篇讲的是散列表暂不涉及红黑树。
使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容的时候依然会保持链表原本的顺序,避免了链表成死环的问题。原本A-〉B,扩容之后依然能保持链表的顺序。
在java7版本的HashMap中,当在多线程环境下可能会造成死循环,原因是扩容转移后前后链表顺序倒置,在转移过程中修改了原来链表中节点的引用关系。
Java 8在相同的多线程环境下不会导致死循环,因为在扩容转移之后,链表顺序保持不变,并且保持了以前节点的引用关系。
面试官:那是不是意味着java8的HashMap可以在多线程环境下运行?
就算在java8版本HashMap得以修复一部分缺陷,但是通过源码得知put/get这两个方法都没有加入同步锁,因此出现出现最极端的情况是:无法保证上一秒put的值,下一秒get的时候还是原来的值,线程安全依然无法保障。
面试官:可以,可以今天你是回答得最好的。不过HashMap初始化容量是多少?
16
面试官:知道为什么是16吗?
突然脑子像被电了一下,噢噢,都想起来了看我这记性
在1.8jdk236行中有这行1<<4
使用位运算结果为16。
面试官:为啥用位运算呢,直接写16不好吗?
我赶紧把源码从头想一边,有了。
值一般最好要填2的幂,这样做的好处在于位运算比简单的数字计算效率真的高了好多,而之所以选择16位初始大小,是为了服务将Key映射到index的算法。
我前面说了所有的key我们都会拿到他的hash,但是我们怎么尽可能的得到一个均匀分布的hash呢?
是的我们通过Key的HashCode值去做位运算,通过用位的方式也提高了不少。
我们再看下index的计算公式:index = HashCode(Key) & (Length- 1)
面试官:为啥用16不用其他的值作为初始值呢?
因为在使用不是2的幂的数字的时候,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。
只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。
这是为了实现均匀分布。
面试官:用HashMap的例子说明,当我们重写equals方法的时候需要重写hashCode方法的?
因为在java中,所有的对象都是继承于Object类。Ojbect类中有两个方法equals、hashCode,这两个方法都是用来比较两个对象是否相等的。
在未重写equals方法我们是继承了object的equals方法,那里的 equals是比较两个对象的内存地址,显然我们new了2个对象内存地址肯定不一样
对于值对象,==比较的是两个对象的值
对于引用对象,比较的是两个对象的地址
大家是否还记得我说的HashMap是通过key的hashCode去寻找index的,那index一样就形成链表了,也就是说”老公“和”老婆“的index都可能是2,在一个链表上的。
我们去get的时候,他就是根据key去hash然后计算出index,找到了2,那我怎么找到具体的”老婆“还是”老公“呢?
equals!是的,所以如果我们对equals方法进行了重写,建议一定要对hashCode方法重写,以保证相同的对象返回相同的hash值,不同的对象返回不同的hash值。
不然一个链表的对象,你哪里知道你要找的是哪个,到时候发现hashCode都一样,这不是完犊子嘛。
面试官:你能说说在场景中如果需要用到线程安全怎么办?
在这样的场景中,我们通常使用hashtable或者current HashMap,但是由于前者的并发性,基本上没有使用场景,所以当线程不安全时,我们都使用correnthashmap解决。在correnthashmap1.8的get方法上,使用了锁的机制
我今天的这篇文章中就重点介绍了在面试中常见的题,只要考官问到HashMap那么基本没有问题。
如果帮到你请关注,转发
为什么使用HashMap需要重写hashcode和equals方法_java常见面试题敲黑板了,HashMap最全的整理,大厂必考...相关推荐
- 为什么使用HashMap需要重写hashcode和equals方法_为什么要重写 hashcode 和 equals 方法?...
1. 通过Hash算法来了解HashMap对象的高效性 2. 为什么要重写equals和hashCode方法 3. 对面试问题的说明 <Java 2019 超神之路> <Dubbo ...
- 为什么使用HashMap需要重写hashcode和equals方法_为什么要重写hashcode和equals方法?你能说清楚了吗...
我在面试Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分,有 ...
- 为什么使用HashMap需要重写hashcode和equals方法_《进大厂系列》系列-HashMap
你知道的越多,你不知道的越多 点赞再看,养成习 正文 一个婀娜多姿,穿着衬衣的小姐姐,拿着一个精致的小笔记本,径直走过来坐在我的面前. 看着眼前这个美丽的女人,心想这不会就是Java基础系列的面试官吧 ...
- 为什么使用HashMap需要重写hashcode和equals方法_不同时重写equals和hashCode又会怎样?听听过来人的经验...
可能一问到equals和hashCode相关的问题,就会有人讲他们的自反性,对称性,一致性,传递性等几条约定了,此时我不得不佩服,这么多约定竟然都能记得,但我不知道你是不是真的理解呢. 一.我不同时重 ...
- 为什么使用HashMap需要重写hashcode和equals方法_最通俗易懂搞定HashMap的底层原理...
HashMap的底层原理面试必考题. 为什么面试官如此青睐这道题? HashMap里面涉及了很多的知识点,可以比较全面考察面试者的基本功,想要拿到一个好offer,这是一个迈不过的坎,接下来我用最通俗 ...
- HashMap存自定义对象为什么要重写 hashcode 和 equals 方法?
HashMap的k放过自定义对象么? 当我们把自定义对象存入HashMap中时,如果不重写hashcode和equals这两个方法,会得不到预期的结果. class Key{private Integ ...
- HashMap存储自定义类型键值: 重写HashCode和equals方法
一个团体作为一个HashMap的key值,若团体成员的姓名年龄相同,则看作key值相同 因为是自定义类,所以需要重写HashCode和equals方法 public class RedVelvet { ...
- 为什么要重写 hashcode 和 equals 方法?
我在面试Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分,有 ...
- 重写hashcode和equals方法
一.前言 我们都知道,要比较两个对象是否相等时需要调用对象的equals()方法,即判断对象引用所指向的对象地址是否相等,对象地址相等时,那么与对象相关的对象句柄.对象头.对象实例数据.对象类型数据等 ...
最新文章
- 面试官扎心一问:知道 CopyOnWriteArrayList 吗?
- -mkdir 创建目录 Usage:hdfs dfs -mkdir [-p] < paths> 选项:-p 很像Unix mkdir -p,沿路径创建父目录。
- 项目经理的10条项目管理心得
- [Java基础]生产者和消费者模式概述与案例分析
- 殊途同归? 亚马逊和微软都选了云业务老大当公司CEO
- 【数论】—— 整数质因子分解
- pip 指定版本安装
- 江苏职称计算机考试错做题,江苏省职称计算机考试word注意点.doc
- HDU 4371 Alice and Bob
- 决策树(十二)--XGBoost
- 按键手机java下载_经典按键java手机游戏
- 离散数学课程对应目录
- fw325r虚拟服务器连接失败,fw325r重置后不能联网怎么办?
- OpenCV-Python<八> 图像平滑处理
- 如何彻底的卸载anaconda(包括配置文件)
- 【毕业设计】基于stm32的智能扫地机器人设计与实现 - 单片机 物联网
- 利用adb 命令回到手机端某个app的页面
- dm_svc.conf文件介绍
- 【问题记录】js 更改数组中某字段名
- 华为“阳阴面”:阳面是镜子 阴面是体系上的霉斑
热门文章
- Dora.Interception,为.NET Core度身打造的AOP框架 [1]:更加简练的编程体验
- skynet 控制台管理使用技巧
- 科技下的仓库,数据库
- ZOJ 3820 Building Fire Stations
- Eclipse中SVN的安装步骤(两种)和使用方法 (转)
- Python descriptor
- CodeSmith实用技巧(十五):使用快捷键
- ntpdate[27350]: no server suitable for synchronization found
- 结合Scikit-learn介绍几种常用的特征选择方法
- Kaggle心得(二)