我们时常会判断一个元素是否相等重复,可以用equals方法。

每增加一个元素,我们就可以通过equals方法判断集合中的每一个元素是否重复,但是如果集合中有10000个元素了,我们每添加一个元素的时候,就需要进行10000此的equals方法的调用,显示效率非常的低下了。

于是基于这种问题,java集合的设计者采用了哈希表来实现。

哈希表也称为散列算法,是依据数据特定算法产生的结果直接指定到一块地址上,这个结果由hashCode方法产生。

这样一来,当集合每添加一个新的元素的时候,就可以通过hashCode方法直接定位到该存放的物理位置上,而不需要大量的equals板的比较。

上面说到了hashCode方法,它是Object类中的一个被native修饰的方法,

那么也就是说,我们每个对象都会继承了这个方法,我们也就可以重写它了

Object类的hashCode方法代码:

public native int hashCode();

hashCode的比较方式

  比如下方是在用HashSet存值

  1. 计算出来的位置上,如果这个位置上没有元素,它就可以直接存储在这个位置上了,不用进行任何的比较
  2. 如果这个位置有元素了,就调用它的(这个对象)equals方法与新的元素进行比较,相同的话就不存了
  3. 如果equals方法比较后,不相同,也就是放生了hashKey相同,导致冲突的情况。那么就在这个hashKey的地方产生一个链表,将所有产生相同的hashKey的对象添加到这个链表上,串在一起(很少会出现)。这样一来实际上我们调用equals方法的几率就大大降低了。

下面以简单的图来表示

这里有A B C D四个对象,分别通过hashCode方法产生了3个值

注意A和B对象调用hashCode产生的值是相同的,即 A.hashCode = B.hashCode()= 0x001

发生了哈希冲突,这时候由于最先插入了A,在插入B的时候,我们发现B要插入A的位置,而A已经插入,也就是这个位置已经有对象了。

这个时候就通过调用equals方法判断A和B是否相同,如果相同就不插入B,如果不同则将B插入到A后面的位置。

所以对于equals方法和hashCode方法有如下的要求:

一、hashCode要求

  1. 在程序运行期间,只要对象(字段)变化不会影响到equals方法的决策结果,那么在这个期间,无论调用多少次hashCode,都必须返回相同的散列码的hashCode
  2. 通过equals调用返回true的2个对象的hashCode一定相同
  3. 通过equals返回false的2个对象的hashCode不需要不同,也就是允许hashCode相同。

因此得到以下结论

  • 两个对象相等,其hashCode一定相同
  • 两个对象不相等,其hashCode可能相等
  • hashCode相等的两个对象,不一定相同
  • hashCode不相等的两个对象,一定不同

可能会有人疑问,对于不能重复的集合,为什么不直接通过 hashCode 对于每个元素都产生唯一的值,如果重复就是相同的值,这样不就不需要调用 equals 方法来判断是否相同了吗?

  实际上对于元素不是很多的情况下,直接通过 hashCode 产生唯一的索引值,通过这个索引值能直接找到元素,而且还能判断是否相同。比如数据库存储的数据,ID 是有序排列的,我们能通过 ID 直接找到某个元素,如果新插入的元素 ID 已经有了,那就表示是重复数据,这是很完美的办法。但现实是存储的元素很难有这样的 ID 关键字,也就很难这种实现 hashCode 的唯一算法,再者就算能实现,但是产生的 hashCode 码是非常大的,这会大的超过 Java 所能表示的范围(因为返回值是int类型,大小只能是232),很占内存空间,所以也是不予考虑的。

二、重写hashCode

我们应该注意:

  • 不同对象的hashCode码应该尽量不同,避免hash冲突,也就是算法获得元素要尽量均匀。
  • hash值是一个int类型,在java中占用4个字节,也就是232 次方,要避免溢出

下面是String的hashCode实现

public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value; for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

这里有个数字 31 ,为什么选择31作为乘积因子,而且没有用一个常量来声明?主要原因有两个:

  ①、31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。

  ②、31可以被 JVM 优化,31 * i = (i << 5) - i。因为移位运算比乘法运行更快更省性能。

  具体解释可以参考这篇文章

ps:

  对于Map集合,我们可以选择Java中的基本类型,还有引用类型String作为key,因为它们都按照规范重写了equals方法和hashCode方法。

但是如果我们自定义对象作为key,那么一定要覆盖equals方法和hahshCode方法,要不然会有未知的suprise等着你。

浅谈原理--hashCode方法的更多相关文章

  1. Unity iOS打开AppStore评星页面,浅谈Application.OpenURL()方法。

    http://fairwoodgame.com/blog/?p=38 Unity iOS打开AppStore评星页面,浅谈Application.OpenURL()方法. Posted in  Uni ...

  2. 浅谈 underscore 内部方法 group 的设计原理

    前言 真是天一热什么事都不想干,这个月只产出了一篇文章,赶紧写一篇压压惊! 前文(https://github.com/hanzichi/underscore-analysis/issues/15)说 ...

  3. 浅谈 JSON.stringify 方法

    一.前言 最近项目中,遇到需要将对象转换成字符串进行传递,上次写过一篇文章关于json字符串转换成json对象,json对象转换成字符串,值转换成字符串,字符串转成值.当时主要是用在有时候处理字符串和 ...

  4. 浅谈vuex使用方法(vuex简单实用方法)

    Vuex 是什么? Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.Vuex 也集成到 Vu ...

  5. 浅谈Arrays.asList()方法的使用

    首先,该方法是将数组转化为list.有以下几点需要注意: (1)该方法不适用于基本数据类型(byte,short,int,long,float,double,boolean) (2)该方法将数组与列表 ...

  6. 欧莱雅浅谈OC中方法调用的顺序中的Category

    OC特有的分类Category,依赖于类.它可以在不改变原来的类内容的基础上,为类增加一些方法.分类的使用注意: (1)分类只能增加方法,不能增加成员变量: (2)在分类方法的实现中可以访问原来类中的 ...

  7. Python进阶之浅谈内置方法(补充)

    目录 列表类型的内置方法 元组类型的内置方法 字典类型的内置方法 集合类型的内置方法 列表类型的内置方法 1.作用:描述名字,说的话等 2.定义方式 s=['tim','age'] s=str('ti ...

  8. Python进阶之浅谈内置方法

    目录 有序or无序和可变or不可变 数字类型内置方法 整形 浮点型 字符串类型内置方法 有序or无序和可变or不可变 有序:有索引 无序:无索引 可变:变量值变,id不变 不可变:变量值变,id也变 ...

  9. 浅谈 js 对象 toJSON 方法

    前些天在<浅谈 JSON.stringify 方法>说了他的正确使用姿势,今天来说下 toJSON 方法吧.其实我觉得这货跟 toString 一个道理,他是给 stringify 方法字 ...

随机推荐

  1. 实现一个正则表达式引擎in Python(一)

    前言 项目地址:Regex in Python 开学摸鱼了几个礼拜,最近几天用Python造了一个正则表达式引擎的轮子,在这里记录分享一下. 实现目标 实现了所有基本语法 st = 'AS342abc ...

  2. linux环境下Nginx的配置及使用

    切换到目录/usr/local/nginx/sbin,/usr/local为nginx的默认安装目录 #启动 ./nginx #查看命令帮助 ./nginx -h 验证配置文件状态 ./nginx - ...

  3. 使用python asyncio+aiohttp做接口测试(TODO)

    线程是操作系统层面的“并行”, 协程是应用程序层面的“并行”. 协程本质上就是:提供一个环境,保存一些需要等待的任务,当这些任务可以执行(等待结束)的时候,能够执行.再等待的过程中,程序可以执行别的任 ...

  4. tomcat 报错出现 jar not loaded. See Servlet Spec 2.3, section 9.7.2. Offending class: javax/servlet/Servlet.class

    这是你导入的jar的问题 一般情况下是导入的包tomcat已经存在 也就是说 不需要你再次导入 所以你现在要做的是删除你所导的包 解决方案:删除你的web项目导入的这两个jar文件 jsp-api.j ...

  5. 23种设计模式之适配器模式(Adapter Pattern)

    适配 即在不改变原有实现的基础上,将原先不兼容的接口转换为兼容的接口.例如:二转换为三箱插头,将高电压转换为低电压等. 动机(Motivate):    在软件系统中,由于应用环境的变化,常常需要将“ ...

  6. 命名对象继承1-验证Create*命名对象安全属性的传递

    windows核心编程 第5版 48页 下半部写道 进程B调用CreateMutex时,它会向函数传递安全属性信息和第二参数.如果已经存在一个指定名称的对象,这些对象就会被忽略 于是我通过代码来验证这 ...

  7. 玩转 SpringBoot 2 之整合 JWT 上篇

    前言 该文主要带你了解什么是 JWT,以及JWT 定义和先关概念的介绍,并通过简单Demo 带你了解如何使用 SpringBoot 2 整合 JWT. 介绍前在这里我们来探讨一下如何学习一门新的技术, ...

  8. ELK 学习笔记之 Logstash之output配置

    Logstash之output配置: 输出到file 配置conf: input{ file{ path => "/usr/local/logstash-5.6.1/bin/spark ...

  9. win7环境搭建以太坊私链

    如何创建私链: 创建创世配置文件: 首先需要创建一个“创世”json配置文件,此文件描述了创世区块的一些参数.下面就是文件中的内容: { "coinbase": "0x0 ...

  10. C++学习笔记-预备知识

    1.1 C++简介 C++融合3种不同的编程方式:C语言代表的过程性语言.C++在C语言基础上添加的类代表的面向对象语言.C++模板支持的广泛编程. 1.2 C++简史 1.2.1 C语言 Ritch ...