简述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. Java登录专题-----创建用户(一)

    Java登录专题-----创建用户(一) 我来填坑了 创建用户 入参 应该包括: 用户姓名,用户密码,用户手机号,用户所属机构 用户版本号,角色id 出参: 没有 数据结构: JavaBean    ...

  2. 教你用canvas打造一个炫酷的碎片切图效果

    前言 今天分享一个炫酷的碎片式切图效果,这个其实在自己的之前的博客上有实现过,本人觉得这个效果还是挺炫酷的,这次还是用我们的canvas来实现,代码量不多,但有些地方还是需要花点时间去理解的,需要点数 ...

  3. 7. url反向解析和静态文件

    一.代码中url出现的位置 1.模版[html]中 1.<a href='urk'>超链接点击跳转<a/> 2.<form action='url' method='po ...

  4. dns隧道攻击原理及常用工具流量分析

    DNS协议是一种请求应答协议,也是一种可用于应用层的隧道技术.虽然DNS流量的异常变化可能会被发现,但是在基于传统socket隧道已经濒临淘汰,TCP.UDP通信大量被安全设备拦截的大背景下,DNS. ...

  5. 介绍一个jmeter录制脚本谷歌插件 —— metersphere-chrome-plugin

    该插件可将用户在浏览器操作时的 HTTP 请求记录下来并生成 JMX 文件(JMeter 脚本文件). 1. 插件解压 插件下载链接: https://pan.baidu.com/s/14nGb_s9 ...

  6. 【Virt.Contest】CF1215(div.2)

    第二次打虚拟赛. CF 传送门 T1:Yellow Cards 黄色卡片 中规中矩的 \(T1\). 首先可以算出一个人也不罚下时发出的最多黄牌数: \(sum=a1*(k1-1)+a2*(k2-1) ...

  7. KubeEdge的云边协同设计原理

    1.云端组件与K8s Master的关系 cloudCore和K8s master,非侵入的映射 2.EdgeController详解 -边缘节点管理 -应用状态元数据云边协同 3.DeviceCon ...

  8. X活手环的表盘自定义修改

    文章用到的所有工具及软件成品 前言 前几天我在某宝买了一个智能手环,无奈软件中的表盘太少,所有我想着修改一下app中的资源文件. 反编译APK 这里反编译APK用apktool工具就可以. apkto ...

  9. 第2-4-7章 docker安装WorkBench-规则引擎Drools-业务规则管理系统-组件化-中台

    目录 8. WorkBench 8.1 WorkBench简介 8.2 安装方式 8.2.1 传统方式安装 8.2.2 docker安装drools workbench 8.3 使用方式 8.3.1 ...

  10. 【SQL知识】SQL中的join操作总结:内连接、外连接(左右全)

    一.含义 基于表之间的共同字段,把来自两个或多个表的行结合起来 二.分类 内连接:join / inner join 外连接:left join / right join / full outer j ...