简述HashSet的扩容机制以及我们在重写equals()的时候为何会重写hashcode()
简述HashSet的扩容机制以及我们在重写equals()的时候为何会重写hashcode()
摘要:在背面试知识点的时候存在这样一条著名的面试题:我们重写equals()的时候为什么要重写hashcode()?答案往往是:二者是配套使用的,只重写equals()不重写hashcode()会导致判断的时候出错误,这是一个非常模糊的回答,直接记住往往会导致我们持续的错误理解,今天在看HashSet的源码的时候发现了其本质上的原因,特来记录。
1.关于HashSet的基础知识
1.1.HashSet的数据结构
众所周知HashSet是一种Java集合类型,它是无序的(即插入顺序和输出顺序不同),并且是无重复元素的,巧妙的使用这个集合类型可以达到去重的目的,特别是在写算法的时候有奇效,但是它为什么有这种去重的功能呢?它底层又是怎么实现的呢?现在我们来解答一下。
HashSet的底层是使用HashMap实现的,而HashMap的数据结构基础是哈希表,我们可以说它的存储结构就是哈希表,但是它的节点和哈希表有所不同,其节点内部是分成了键值对的形式,这样我们就可以通过键值对中的键来灵活的获取值了,其中键是不允许重复的,而值是可以重复的,可以说HashMap中的键是通过某种手段保证了其唯一性,而HashMap实际上属于一种冗余的数据结构,这种冗余的数据结构可以通过添加某种性质退化成另一种性质的数据结构,当我们让其中的值失效的时候,也就是说我们让键值对中的值这一项失去意义的时候,它就退化成一个普通的哈希表了,在Java中通过这种普通的哈希表实现了一种叫做HashSet的集合。也就是说HashSet的底层是使用HashMap实现的。我们在学习数据结构的时候都知道Hash表是这样的:
1.2.HashSet的add方法过程
HashSet的add方法主要分为以下几个步骤:
- 调用插入元素的HashCode方法,生成元素的HashCode
- 根据HashCode做计算,根据一个算法生成插入元素的Hash值
- 根据Hash值进行进一步计算,得到即将插入到哈希表中表体的索引位置,也就是节点数组上的位置
- 当相应的索引位置上面没有节点的时候直接放进去并让总体size加1,表示进入了一个新节点
- 当相应的索引位置上有节点的时候,说明发生了hash碰撞,这个时候会进行一个判重操作
- 判重操作的过程是首先判断两个元素的hash值是否相等,如果不相等说明这两个元素一定不是同一个元素,这个时候直接向后边的链表或者红黑树添加
- 如果hash相等的话说明两个元素可能是同一个对象,这个时候hash值的判断不能决定判断出两个元素是否是同一个对象,需要进行进一步判断,这个时候会调用元素的equals方法进行判重,equals方法是我们自己书写的,如果equals方法判断认为这两个元素是同一个元素的话,那么就真的是了,否则不是
2.为什么重写equals之后我们需要重写hashcode方法
hashcode协助生成hash,而hash是协助equals缩小判断范围的,如果两个对象的hash值不同,两个对象必然不同,而后就不用进行equals方法了,简而言之hash值一定是和对象相关的,必须能够保证hash不同,使用equals一定不同,因此equals的底层实际上是和hash值有关联的,我们这里需要明白一件事情就是equals在不重写的情况下是根据地址进行判断相等的,而默认的hashcode也是根据地址生成的,hash相当于一个根据地址生成的有误差的值,根据这个值进行判断,如果不相同,能够保证地址一定不相同,这样我们可以避免使用地址去判断,因为地址很长,对比起来更加消耗时间,并且一个个的去对比地址的话,需要在hashset上的某一个链上的所有元素去对比,通过hashset对比能够保证减少不必要的对比。需要注意的是两者一定是要有关联的,如果我们称判重需要的特征为区分因子的话,hash相当于一个根据区分因此产出的存在判断误差的二级判断因子,通过这个二级判断因子可以避免使用更复杂的一级判断因子去做更细致的判断,进而影响性能,但是这里要求hash这个判断因子一定要根据一级判断因子产生,否则就没有意义了。
当我们修改了equals之后不重写hashcode的话,相当于更改了一级判断因子没有判断二级判断因子,这样会导致hash的判断是没有意义的,两次判断没有关系,举个例子,hash是根据地址生成的,但是equals是根据对象中的某个字段进行的判断,如果一个字段A的值都为“123”的话那么这两个对象就是相同的,这是我们自己的判断逻辑,而hash还是根据地址生成的,两个不同的对象的不同地址生成的hash大概率是不同的,因此就会出现equals应该相同的,但是在hash判断处就判断成不同了,这种现象是违反了我们的设计逻辑的。我们的设计逻辑应该保证:equals判断相同的,hash一定是判断相同;hash判断相同的,equals可能判断出不同;hash判断出不同的,equals一定也不同,当出现equals判断相同的,hash判断出不同就会导致在hash插入的时候,判断优先级的判断因子被打乱,导致我们不能按照我们自己设计的判断逻辑插入数据,就会导致我们在使用hashset的时候出现问题。
简述HashSet的扩容机制以及我们在重写equals()的时候为何会重写hashcode()的更多相关文章
- HashSet扩容机制在时间和空间上的浪费,远大于你的想象
一:背景 1. 讲故事 自从这个纯内存项目进了大客户之后,搞得我现在对内存和CPU特别敏感,跑一点数据内存几个G的上下,特别没有安全感,总想用windbg抓几个dump看看到底是哪一块导致的,是我的代 ...
- HashSet保证元素唯一原理以及HashMap扩容机制
一.HashSet保证元素唯一原理: 依赖于hashCode()和equals()方法1.唯一原理: 1.1 当HashSet集合要存储元素的时候,会调用该元素的hashCode()方法计算哈希值 1 ...
- java集合专题 (ArrayList、HashSet等集合底层结构及扩容机制、HashMap源码)
一.数组与集合比较 数组: 1)长度开始时必须指定,而且一旦指定,不能更改 2)保存的必须为同一类型的元素 3)使用数组进行增加/删除元素-比较麻烦 集合: 1)可以动态保存任意多个对象,使用比较方便 ...
- 浅谈JAVA中HashMap、ArrayList、StringBuilder等的扩容机制
JAVA中的部分需要扩容的内容总结如下:第一部分: HashMap<String, String> hmap=new HashMap<>(); HashSet<Strin ...
- Java常见集合的默认大小及扩容机制
在面试后台开发的过程中,集合是面试的热话题,不仅要知道各集合的区别用法,还要知道集合的扩容机制,今天我们就来谈下ArrayList 和 HashMap的默认大小以及扩容机制. 在 Java 7 中,查 ...
- 面试题: Java中各个集合类的扩容机制
个人博客网:https://wushaopei.github.io/ (你想要这里多有) Java 中提供了很多的集合类,包括,collection的子接口list.set,以及map等.由于它 ...
- ArrayList源码解析(二)自动扩容机制与add操作
本篇主要分析ArrayList的自动扩容机制,add和remove的相关方法. 作为一个list,add和remove操作自然是必须的. 前面说过,ArrayList底层是使用Object数组实现的. ...
- 深入理解HashMap的扩容机制
什么时候扩容: 网上总结的会有很多,但大多都总结的不够完整或者不够准确.大多数可能值说了满足我下面条件一的情况. 扩容必须满足两个条件: 1. 存放新值的时候当前已有元素的个数必须大于等于阈值 2. ...
- HashMap底层结构、原理、扩容机制
https://www.jianshu.com/p/c1b616ff1130 http://youzhixueyuan.com/the-underlying-structure-and-princip ...
- ArrayList的扩容机制
一.ArrayList的扩容机制 1.扩容的计算方式是向右位移,即:newSize = this.size + (this.size>>1).向右位移,只有在当前值为偶数时,才是除以2:奇 ...
随机推荐
- winscp报错Server sent passive reply with unroutable address. Using server address instead
找了一堆没用. 最后终于 1.使用winSCP连接ftp时,编辑会话,单击高级. 2.进入高级设置之后,单击连接,查看连接模式,把被动模式的勾,勾掉. 3.单击确定,然后保存配置,重新连接FTP,OK
- sql面试50题------(11-20)
文章目录 11.查询至少有一门课与学号为'01'的学生所学课程相同的学生的学号和姓名 12.查询和'01'号同学所学课程完全相同的其他同学的学号 13.查询两门及其以上不及格课程的同学的学号,姓名及其 ...
- Springboot+vue 实现汽车租赁系统(毕业设计二)(前后端项目分离)
文章目录 1.系统功能列表 2.管理员端界面 2.1 商家登录界面 2.2 用户信息管理界面 2.3 汽车管理界面 2.4 订单界面 2.5 汽车图形报表 2.6 优惠券新增界面 3.普通用户界面 3 ...
- 5.@pytest.mark.parametrize()数据驱动
简介: pytest.mark.parametrize 是 pytest 的内置装饰器,它允许你在 function 或者 class 上定义多组参数和 fixture 来实现数据驱动. @pytes ...
- JVM学习笔记——垃圾回收篇
JVM学习笔记--垃圾回收篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的垃圾回收部分 我们会分为以下几部分进行介绍: 判断垃圾回收对象 垃圾回收算法 分代垃圾回收 垃圾回收器 ...
- 搜索"xxxx"的进程,同时杀进程
一.搜索"xxxx"的进程,同时杀进程,命令如下: ps -ef|grep xxxx|grep -v 'grep'|awk '{print $2}'|xargs kill -9 命 ...
- mindxdl--common--log_record.go
// Copyright (c) 2021. Huawei Technologies Co., Ltd. All rights reserved.// Package common define co ...
- dafny : 微软推出的形式化验证语言
dafny是一种可验证的编程语言,由微软推出,现已经开源. dafny能够自我验证,可以在VS Code中进行开发,在编辑算法时,写好前置条件和后置条件,dafny验证器就能实时验证算法是否正确. 在 ...
- C#使用Task在Winform建立控件上的提示等待窗口,实现局部等待加载,不影响主线程(二)
效果图: 源码:(处理了亿点点细节) 链接:https://pan.baidu.com/s/18S1IgQBOyXgeGvhnU3nrKQ?pwd=jpq9提取码:jpq9 作者:兮去博客出处:htt ...
- 记录下批处理bat脚本获取打包发布问题
最近做了个Jenkins配合Gitlab自动部署Java项目到Windows Server服务器. Jenkins和Gitlab在Linux下,好一顿折腾,先记录下脚本,其余后续补充吧. 把Java项 ...