最近几天,在这样的大环境下显得疲惫不堪,但是我还是写下了这篇文章,希望对任何人都有用。

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最全的整理,大厂必考...相关推荐

  1. 为什么使用HashMap需要重写hashcode和equals方法_为什么要重写 hashcode 和 equals 方法?...

    1. 通过Hash算法来了解HashMap对象的高效性 2. 为什么要重写equals和hashCode方法 3. 对面试问题的说明 <Java 2019 超神之路> <Dubbo ...

  2. 为什么使用HashMap需要重写hashcode和equals方法_为什么要重写hashcode和equals方法?你能说清楚了吗...

    我在面试Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分,有 ...

  3. 为什么使用HashMap需要重写hashcode和equals方法_《进大厂系列》系列-HashMap

    你知道的越多,你不知道的越多 点赞再看,养成习 正文 一个婀娜多姿,穿着衬衣的小姐姐,拿着一个精致的小笔记本,径直走过来坐在我的面前. 看着眼前这个美丽的女人,心想这不会就是Java基础系列的面试官吧 ...

  4. 为什么使用HashMap需要重写hashcode和equals方法_不同时重写equals和hashCode又会怎样?听听过来人的经验...

    可能一问到equals和hashCode相关的问题,就会有人讲他们的自反性,对称性,一致性,传递性等几条约定了,此时我不得不佩服,这么多约定竟然都能记得,但我不知道你是不是真的理解呢. 一.我不同时重 ...

  5. 为什么使用HashMap需要重写hashcode和equals方法_最通俗易懂搞定HashMap的底层原理...

    HashMap的底层原理面试必考题. 为什么面试官如此青睐这道题? HashMap里面涉及了很多的知识点,可以比较全面考察面试者的基本功,想要拿到一个好offer,这是一个迈不过的坎,接下来我用最通俗 ...

  6. HashMap存自定义对象为什么要重写 hashcode 和 equals 方法?

    HashMap的k放过自定义对象么? 当我们把自定义对象存入HashMap中时,如果不重写hashcode和equals这两个方法,会得不到预期的结果. class Key{private Integ ...

  7. HashMap存储自定义类型键值: 重写HashCode和equals方法

    一个团体作为一个HashMap的key值,若团体成员的姓名年龄相同,则看作key值相同 因为是自定义类,所以需要重写HashCode和equals方法 public class RedVelvet { ...

  8. 为什么要重写 hashcode 和 equals 方法?

    我在面试Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分,有 ...

  9. 重写hashcode和equals方法

    一.前言 我们都知道,要比较两个对象是否相等时需要调用对象的equals()方法,即判断对象引用所指向的对象地址是否相等,对象地址相等时,那么与对象相关的对象句柄.对象头.对象实例数据.对象类型数据等 ...

最新文章

  1. 面试官扎心一问:知道 CopyOnWriteArrayList 吗?
  2. -mkdir 创建目录 Usage:hdfs dfs -mkdir [-p] < paths> 选项:-p 很像Unix mkdir -p,沿路径创建父目录。
  3. 项目经理的10条项目管理心得
  4. [Java基础]生产者和消费者模式概述与案例分析
  5. 殊途同归? 亚马逊和微软都选了云业务老大当公司CEO
  6. 【数论】—— 整数质因子分解
  7. pip 指定版本安装
  8. 江苏职称计算机考试错做题,江苏省职称计算机考试word注意点.doc
  9. HDU 4371 Alice and Bob
  10. 决策树(十二)--XGBoost
  11. 按键手机java下载_经典按键java手机游戏
  12. 离散数学课程对应目录
  13. fw325r虚拟服务器连接失败,fw325r重置后不能联网怎么办?
  14. OpenCV-Python<八> 图像平滑处理
  15. 如何彻底的卸载anaconda(包括配置文件)
  16. 【毕业设计】基于stm32的智能扫地机器人设计与实现 - 单片机 物联网
  17. 利用adb 命令回到手机端某个app的页面
  18. dm_svc.conf文件介绍
  19. 【问题记录】js 更改数组中某字段名
  20. 华为“阳阴面”:阳面是镜子 阴面是体系上的霉斑

热门文章

  1. Dora.Interception,为.NET Core度身打造的AOP框架 [1]:更加简练的编程体验
  2. skynet 控制台管理使用技巧
  3. 科技下的仓库,数据库
  4. ZOJ 3820 Building Fire Stations
  5. Eclipse中SVN的安装步骤(两种)和使用方法 (转)
  6. Python descriptor
  7. CodeSmith实用技巧(十五):使用快捷键
  8. ntpdate[27350]: no server suitable for synchronization found
  9. 结合Scikit-learn介绍几种常用的特征选择方法
  10. Kaggle心得(二)