Java HashCode详解
一、为什么要有Hash算法
Java中的集合有两类,一类是List,一类是Set。List内的元素是有序的,元素可以重复。Set元素无序,但元素不可重复。要想保证元素不重复,两个元素是否重复应该依据什么来判断呢?用Object.equals方法。但若每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说若集合中已有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。于是Java采用了哈希表的原理。哈希(Hash)是个人名,由于他提出哈希算法的概念就以他的名字命名了。
二、Hash算法原理
HashCode的官方文档定义:
hashCode 的常规协定是:
- 1.在 Java 应用程序执行期间,只要对象的 equals 比较操作所用的信息没有被修改,那么在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
- 2.如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
- 3.如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法不一定要产生不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
- 1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
- 例如内存中有这样的位置
- 0 1 2 3 4 5 6 7
- 而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
- 但如果用hashcode那就会使效率提高很多。
- 我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。
- 2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
- 也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
- 那么。重写了equals(),为什么还要重写hashCode()呢?
- 想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到
当Set接收一个元素时根据该对象的信息算出hashCode,看它属于哪一个区间,在这个区间里调用equeals方法。

确实提高了效率。但一个面临问题:若两个对象equals相等,但不在一个区间,根本没有机会进行比较,会被认为是不同的对象。所以Java对于eqauls方法和hashCode方法是这样规定的:
1 如果两个对象相同,那么它们的hashCode值一定要相同。也告诉我们重写equals方法,一定要重写hashCode方法。
2 如果两个对象的hashCode相同,它们并不一定相同,这里的对象相同指的是用eqauls方法比较。
三、hashCode方法的分析
哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率。在Java的Object类中有一个方法:
- public native int hashCode();
为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法。根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现。
对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode。在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。
为什么这么说呢?考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不允许重复的元素存在)
也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。下面这段代码是java.util.HashMap的中put方法的具体实现:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } |
put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。
因此有人会说,可以直接根据hashcode值判断两个对象是否相等吗?肯定是不可以的,因为不同的对象可能会生成相同的hashcode值。虽然不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等,如果两个对象的hashcode值不等,则必定是两个不同的对象。如果要判断两个对象是否真正相等,必须通过equals方法。
也就是说对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
如果两个对象的hashcode值相等,则equals方法得到的结果未知。
四、覆写equals时总要覆盖HashCode
如果不覆盖会怎么样,这样就违反了第二条规定,相等的对象必须具有相等的散列码
如果不写,即使是相等的对象,返回的就是两个不同的散列码
Java HashCode详解的更多相关文章
- java集合详解
1.java集合框架的层次结构 Collection接口: Set接口: HashSet具体类 LinkedHashSet具体类 TreeSet具体类 List接口: ArrayList具体类 L ...
- 【Java入门提高篇】Day34 Java容器类详解(十五)WeakHashMap详解
源码详解系列均基于JDK8进行解析 说明 在Java容器详解系列文章的最后,介绍一个相对特殊的成员:WeakHashMap,从名字可以看出它是一个 Map.它的使用上跟HashMap并没有什么区别,所 ...
- Java集合详解3:Iterator,fail-fast机制与比较器
Java集合详解3:Iterator,fail-fast机制与比较器 今天我们来探索一下LIterator,fail-fast机制与比较器的源码. 具体代码在我的GitHub中可以找到 https:/ ...
- Java集合详解7:一文搞清楚HashSet,TreeSet与LinkedHashSet的异同
<Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...
- Java集合详解4:一文读懂HashMap和HashTable的区别以及常见面试题
<Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...
- Java集合详解3:一文读懂Iterator,fail-fast机制与比较器
<Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...
- 「万字图文」史上最姨母级Java继承详解
摘要:继承是面向对象软件技术中的一个概念.它使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用. 本文分享自华为云社区<「万字图文」史上最姨母级Java继承详解丨[奔跑吧!JAVA] ...
- Java 集合详解 | 一篇文章解决Java 三大集合
更好阅读体验:Java 集合详解 | 一篇文章搞定Java 三大集合 好看的皮囊像是一个个容器,有趣的灵魂像是容器里的数据.接下来讲解Java集合数据容器. 文章篇幅有点长,还请耐心阅读.如只是为了解 ...
- Java内部类详解
Java内部类详解 说起内部类这个词,想必很多人都不陌生,但是又会觉得不熟悉.原因是平时编写代码时可能用到的场景不多,用得最多的是在有事件监听的情况下,并且即使用到也很少去总结内部类的用法.今天我们就 ...
随机推荐
- java线程池赏析
1.线程池的顶级接口(Executor) 线程池的顶级接口(jdk > 1.5).仅仅定义了方法execute(Runnable). 该方法接收一个Runnable实例,用来执行一个任务,该任务 ...
- C#线程篇---解答线程之惑(2)
我们都知道,在这个行业,追求的就是用最少的时间学最多的知识,这是我写这个系列最想达到的目标,在最快的时间内,帮助更多的人学习更多的线程知识. 前一篇,讲述了线程基础,给大家铺垫了一个基础,这一篇着重介 ...
- Linux系统时间函数
先来说说自己在做工程过程中的一些理解: 1, 输入time_t,输出tm格式的函数 loctaltime(time_t) / gmtime(time_t) 其中localtime会受时区和夏令时影响, ...
- nginx启用stream日志配置文件
主配置文件/etc/nginx/nginx.conf增加内容: stream { log_format proxy '$remote_addr [$time_local] ' '$protocol $ ...
- HTTP返回代码 403 404 500等代表的含义
在网站日志中,我们经常会看到很多返回的http代码,如201.304.404.500等等.可是这些具体的返回的HTTP代码究竟什么含义呢,在此做一下知识普及吧,记不住不要紧,到时候看看就行了,但最主要 ...
- 蓝桥杯 算法提高 学霸的迷宫 经典BFS问题
算法提高 学霸的迷宫 时间限制:1.0s 内存限制:256.0MB 问题描述 学霸抢走了大家的作业,班长为了帮同学们找回作业,决定去找学霸决斗.但学霸为了不要别人打扰,住在一个城 ...
- django project 的快速构建
2003年,堪萨斯(Kansas)州 Lawrence 城中的一个 网络开发小组 ——World Online 小组,为了方便制作维护当地的几个新闻站点(一般要求几天或者几小时内被建立),Adrian ...
- UNDERSTANDING THE GAUSSIAN DISTRIBUTION
UNDERSTANDING THE GAUSSIAN DISTRIBUTION Randomness is so present in our reality that we are used to ...
- Spyder简述
导言 想打造轮子, 就必须要有一套完善的造轮子的工具. 我在jupyter+sciTE的组合里转来转去, 最后还是打算放弃这个组合, 因为离开了自动完成/调用提示/随时随地的访问文档帮助, 前行之路太 ...
- redis写定时任务获取root权限
前提: 1.redis由root用户启动. 2.开启cron的时候,/var/spool/cron linux机器下默认的计划任务,linux会定时去执行里面的任务. 启动服务 :/sbin/serv ...