简述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. 前端框架Vue------>第一天学习(2) v-if

    API:https://cn.vuejs.org/v2/api/#key 文章目录 5.条件渲染 5.1 . v-if 5.2 . v-else-if 6 .列表渲染 7 .事件监听 5.条件渲染 5 ...

  2. 六、Kubernetes节点与 Pod 亲和性

    Kubernetes节点与 Pod 亲和性 一.节点亲和性策略介绍 ​pod.spec.nodeAffinity preferredDuringSchedulingIgnoredDuringExecu ...

  3. Codeforces Round #831 (Div. 1 + Div. 2) A-E

    比赛链接 A 题解 知识点:数学. \(2\) 特判加 \(7\),其他加 \(3\) 直接偶数. 时间复杂度 \(O(1)\) 空间复杂度 \(O(1)\) 代码 #include <bits ...

  4. IP分类与子网划分

    1.IP地址的格式  每一类地址都由两个固定长度的字段组成: (1)网络号 net-id:它标志主机(或路由器)所连接到的网络 (2)主机号 host-id:它标志该主机(或路由器).   最大可指派 ...

  5. pod(五):pod hook(pod钩子)和优雅的关闭nginx pod

    目录 一.系统环境 二.前言 三.pod hook(pod钩子) 四.如何优雅的关闭nginx pod 一.系统环境 服务器版本 docker软件版本 Kubernetes(k8s)集群版本 CPU架 ...

  6. Python基础之面向对象:3、继承与派生

    面向对象 一.三大特征之继承 python三大特征: 封装.继承.多态 三者中继承最为核心,实际应用对,感受较为直观 封装和多态略微抽象 1.继承的概念 继承的含义: ​ 在现实生活中,继承表示人与人 ...

  7. Python基础部分:11、文件和光标移动

    目录 一.文件操作 1.文件的概念 2.代码打开文件的方式 二.文件读写模式 1.'r' 只读模式 read 2.'w' 只写模式 write 3.'a' 尾部追写模式 add 三.文件操作模式 1. ...

  8. Java 多线程写zip文件遇到的错误 write beyond end of stream!

    最近在写一个大量小文件直接压缩到一个zip的需求,由于zip中的entry每一个都是独立的,不需要追加写入,也就是一个entry文件,写一个内容, 因此直接使用了多线程来处理,结果就翻车了,代码给出了 ...

  9. UML建模语言、设计原则、设计模式

    1.UML统一建模语言 定义:用于软件系统设计与分析的语言工具 目的:帮助开发人员更好的梳理逻辑.思路 学习地址:UML概述_w3cschool 官网:https://www.omg.org/spec ...

  10. Ubuntu定时执行python脚本

    Crontab命令 命令选项: crontab -l : 显示某个用户的任务计划 crontab -e :编辑某个用户的任务计划 cron服务 service cron start/stop/rest ...