简述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方法主要分为以下几个步骤:

  1. 调用插入元素的HashCode方法,生成元素的HashCode
  2. 根据HashCode做计算,根据一个算法生成插入元素的Hash值
  3. 根据Hash值进行进一步计算,得到即将插入到哈希表中表体的索引位置,也就是节点数组上的位置
  4. 当相应的索引位置上面没有节点的时候直接放进去并让总体size加1,表示进入了一个新节点
  5. 当相应的索引位置上有节点的时候,说明发生了hash碰撞,这个时候会进行一个判重操作
  6. 判重操作的过程是首先判断两个元素的hash值是否相等,如果不相等说明这两个元素一定不是同一个元素,这个时候直接向后边的链表或者红黑树添加
  7. 如果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()的更多相关文章

  1. HashSet扩容机制在时间和空间上的浪费,远大于你的想象

    一:背景 1. 讲故事 自从这个纯内存项目进了大客户之后,搞得我现在对内存和CPU特别敏感,跑一点数据内存几个G的上下,特别没有安全感,总想用windbg抓几个dump看看到底是哪一块导致的,是我的代 ...

  2. HashSet保证元素唯一原理以及HashMap扩容机制

    一.HashSet保证元素唯一原理: 依赖于hashCode()和equals()方法1.唯一原理: 1.1 当HashSet集合要存储元素的时候,会调用该元素的hashCode()方法计算哈希值 1 ...

  3. java集合专题 (ArrayList、HashSet等集合底层结构及扩容机制、HashMap源码)

    一.数组与集合比较 数组: 1)长度开始时必须指定,而且一旦指定,不能更改 2)保存的必须为同一类型的元素 3)使用数组进行增加/删除元素-比较麻烦 集合: 1)可以动态保存任意多个对象,使用比较方便 ...

  4. 浅谈JAVA中HashMap、ArrayList、StringBuilder等的扩容机制

    JAVA中的部分需要扩容的内容总结如下:第一部分: HashMap<String, String> hmap=new HashMap<>(); HashSet<Strin ...

  5. Java常见集合的默认大小及扩容机制

    在面试后台开发的过程中,集合是面试的热话题,不仅要知道各集合的区别用法,还要知道集合的扩容机制,今天我们就来谈下ArrayList 和 HashMap的默认大小以及扩容机制. 在 Java 7 中,查 ...

  6. 面试题: Java中各个集合类的扩容机制

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) Java 中提供了很多的集合类,包括,collection的子接口list.set,以及map等.由于它 ...

  7. ArrayList源码解析(二)自动扩容机制与add操作

    本篇主要分析ArrayList的自动扩容机制,add和remove的相关方法. 作为一个list,add和remove操作自然是必须的. 前面说过,ArrayList底层是使用Object数组实现的. ...

  8. 深入理解HashMap的扩容机制

    什么时候扩容: 网上总结的会有很多,但大多都总结的不够完整或者不够准确.大多数可能值说了满足我下面条件一的情况. 扩容必须满足两个条件: 1. 存放新值的时候当前已有元素的个数必须大于等于阈值 2. ...

  9. HashMap底层结构、原理、扩容机制

    https://www.jianshu.com/p/c1b616ff1130 http://youzhixueyuan.com/the-underlying-structure-and-princip ...

  10. ArrayList的扩容机制

    一.ArrayList的扩容机制 1.扩容的计算方式是向右位移,即:newSize = this.size + (this.size>>1).向右位移,只有在当前值为偶数时,才是除以2:奇 ...

随机推荐

  1. Error creating bean with name ‘com.ai.ecs.ecop.pointExchange.service.NewGoodsService‘

    Error creating bean with name 'com.ai.ecs.ecop.pointExchange.service.NewGoodsService' 查看服务注册中心的格式是否正 ...

  2. DQL语句排序与分组

    DQL语句排序与分组 一.DQL-排序 排序是计算机内经常进行的一种操作,其目的是将一组"无序"的记录序列调整为"有序"的记录序列.分内部排序和外部排序,若整个 ...

  3. 云原生之旅 - 6)不能错过的一款 Kubernetes 应用编排管理神器 Kustomize

    前言 相信经过前一篇文章的学习,大家已经对Helm有所了解,本篇文章介绍另一款工具 Kustomize,为什么Helm如此流行,还会出现 Kustomize?而且 Kustomize 自 kubect ...

  4. Vue+Go前端后端一体化 企业级微服务网关项目

    Vue+Go前端后端一体化 企业级微服务网关项目(~11) 课程目录: 第1章 拒绝培训机构烂大街项目,冲刺Offer首选[Vue+Go,稀缺组合,赶快上车!]1-1 摒弃千篇一律项目,选我:前后端一 ...

  5. 基于python的数学建模---二维插值的三维图

    import numpy as np from mpl_toolkits.mplot3d import Axes3D import matplotlib as mpl from scipy impor ...

  6. 视觉享受,兼顾人文观感和几何特征的字体「GitHub 热点速览 v.22.46」

    GitHub 上开源的字体不在少数,但是支持汉字以及其他非英文语言的字体少之又少,记得上一个字体还是 霞鹜文楷,本周 B 站知名设计 UP 主开源了的得意黑体在人文观感和几何特征之间找到了美的平衡. ...

  7. 流程编排、如此简单-通用流程编排组件JDEasyFlow介绍

    作者:李玉亮 JDEasyFlow是企业金融研发部自研的通用流程编排技术组件,适用于服务编排.工作流.审批流等场景,该组件已开源(https://github.com/JDEasyFlow/jd-ea ...

  8. WEB入门——信息搜集1-20

    WEB1--查看源码 查看源码即可得flag. WEB2--JS前端禁用 查看源码即可得flag. JavaScript实现禁用的方法简单来说就是当用户使用键盘执行某一命令是返回的一种状态,而这种状态 ...

  9. 【Java EE】Day07 HTML

    一.WEB概念 1.软件架构 C/S:安卓.QQ.迅雷,开发两端 B/S 2.资源分类 静态资源:浏览器内置解析引擎 HTML:展示内容 CSS:页面布局 JavaScript:控制页面元素,产生动态 ...

  10. ChatGPT 可以联网了!浏览器插件下载

    Twitter 用户 An Qu 开发了一款新的 Chrome 插件帮助 ChatGPT 上网,安装插件以后 ChatGPT 就可以联!网!了! 简单来说开启插件后,他可以从网上搜索信息,并且根据用户 ...