简述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:奇 ...
随机推荐
- Vue3 SFC 和 TSX 方式调用子组件中的函数
在开发中会遇到这样的需求:获取子组件的引用,并调用子组件中定义的方法.如封装了一个表单组件,在父组件中需要调用这个表单组件的引用,并调用这个表单组件的校验表单函数或重置表单函数.要实现这个功能,首先要 ...
- 2022年最新编辑Linux基础知识总结
文章目录 1.Linux的目录结构 2.远程操作Linux和上传文件到Linux 3.文本编辑 4.快捷键 5.登录.注销.关机.重启 6.用户管理 6.1 .新用户注册 6.2.使用新用户登录 6. ...
- 谷歌拼音自带lua
function fast_string_banji(argument) return {"快捷1", "快捷2", "快捷3", &quo ...
- spring框架-jdbcTemplate
首先 dao层: dao -bookdao(interface) -bookdaoimpl service层: bookService 实体类对象 entiry-book 测试类 Test-TestB ...
- 几个实用 shell 脚本
1. Dos攻击防范(自动屏蔽攻击 IP) #!/bin/bash DATE=$(date +%d/%b/%Y:%H:%M) LOG_FILE=/usr/local/nginx/logs/demo2. ...
- pycharm系列---基本配置
自动加入头文件 # _*_ coding: utf-8 _*_ # @Time : ${DATE} ${TIME} # @Author : xiechunhui # @Version:V 0.1 # ...
- 《吐血整理》高级系列教程-吃透Fiddler抓包教程(33)-Fiddler如何抓取WebSocket数据包
1.简介 本来打算再写一篇这个系列的文章也要和小伙伴或者童鞋们说再见了,可是有人留言问WebSocket包和小程序的包不会抓,那就关于这两个知识点宏哥就再水两篇文章. 2.什么是Socket? 在计算 ...
- perl哈希嵌套和引用的使用
数组,哈希嵌套 数组,哈希的引用 1.哈希的嵌套和引用 %hash = ( 'group1', {'fruit', 'banana', 'drink', 'orange juice', 'vegeta ...
- 简单的sql注入1
首先查看源码找找思路 发现源码里什么都没有 再使用bp拦截下数据 多次拦截后发现我们在 输入框里输入的等下就是id= 意思是我们这里就可以直接使用get注入了 好像类似于sql-labs上的?id= ...
- vue脚手架安装及依赖
一.安装Vue Cil (脚手架) 需要先安装node.js,这是node官网地址: https://nodejs.org/en/download/ ,node有两种版本一种是稳定版一种开发版 安装完 ...