如何正确实现 Java 中的 HashCode
相等 和 Hash Code
从一般角度来看,Equality 是不错的,但是 hash code 更则具技巧性。如果我们在 hash code上多下点功夫,我们就能了解到 hash code 就是用在细微处去提升性能的。
大部分的数据结构使用equals去检查是否他们包含一个元素。例如:
List<String> list = Arrays.asList("a", "b", "c");
boolean contains = list.contains("b");
这个变量 contains 是true。因为他们是相等的,虽然b的实例化(instance)虽然不完全一样(再说一次,忽略String interning)。
将传递给 contains 的实例与每个元素进行比较很浪费时间。还好,整个这类数据结构使用了一种更高效的方法。它不会将请求的实例与每个元素比较,而是使用捷径,找到可能与之相等的实例,然后只比较这几项。
这个捷径就是哈希码——从对象计算出来的一个能代表该对象的整数值。与哈希码相同的实例不必相等,但相等的实例一定有相同的哈希码。(或者说应该有,我们稍后会对这个问题进行简单讨论)。这类的数据结构常常使用这种技术命名,在名称中加入 Hash 以便识别,其中最具代表性的就是 HashMap。
一般情况下它们会这样进行:
添加一个元素的时候,使用它的哈希码来计算存放在内部数组(称为桶)中的位置(序号)。
另一个不等同的元素如果具有相同的哈希码,它会被放在同一个桶中,与原来那个放在一起,比如把它们放在一个列表中。
如果传递一个实例给 contains 方法,会先计算它的哈希码来找到桶,只有同一个桶中的元素需要与这个实例进行比较。
使用这种方法实现 contains 的情况很少,在理想的状态下根本不需要 equals 比较。
将 equals、hashCode 定义在 Object 中。
关于哈希的一些思考
如果把 hashCode 作为一种快捷方式取决于其是否相等,那么只有一件事情我们需要关心:相等的对象应该有一致的哈希码。
这也是为什么,如果我们覆写 equals 方法,就必须创建一个匹配的 hashCode 实现!此外,实现 equal 应该是依据我们的实现而实现的,这可能会导致没有相同的哈希码,因为他们使用的是 Object 的实现。
hashCode 约定
引用:
对于 hashCode 的一般约定:
在 Java 应用程序中,任何时候对同一对象多次调用 hashCode 方法,都必须一直返回同样的整数,对它提供的信息也用于对象的相等比较,且不会被修改。这个整数在两次对同一个应用程序的执行不中不需要保持一致。
如果两个对象通过 equals(Object) 方法来比较相等,那么这两个对象的 hashCode 方法必须产生同样的整型结果。
如果两个对象通过 equals(Object) 方法比较结果不等,这两个对象的 hashCode 不必产生同不整型结果。然而,开发者应该了解对不等的对象产生不同的整型结果有助于提高哈希表的性能。
第一条反映了 equals 的一致性。第二条是我们在上面提到的要求。第三条陈述了我们下面要讨论的一个重要细节。
实现 hashCode
Person.hashCode 有个很简单的实现:
@Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}
通过计算相关字段的哈希码,再把这些哈希码组合起来得到 person 的哈希码。它们用 Object 的工具函数 hash 来参与计算。
选择字段
然而什么字段才是相关的?这些要求有助于回答这个问题:如果相等的对象必须有相同的哈希码,那么在计算哈希码的时候就不应该使用那些不用于相等性检查的字段。(否则,如果两个对象只有那些字段不同的话,它们会相等但哈希码不同。)
所以用于计算哈希码的那些字段应该是用于相等性比较的那些字段的子集。默认情况下,它们会使用相同的字段,但有几个细节需要考虑。
一致性
第一是一致性要求。它应该经过非常严格的计算。如果有字段产生了变化,哈希码也应该允许变化(对于可变类来说,这往往是不可避免的),依赖哈希的数据结构并未准备应付这种情况。
正如我们在上面看到的那样,哈希码用于确定一个元素的桶,但是如果哈希相关的字段发生变化,并不会立即重新计算哈希码,而且内部的数组也不会更新。
这就意味着,再对一个相等的对象甚至同一个对象的查询会失败!这个数据结构会计算当前的哈希码,这个哈希码与实例存入时的哈希码并不相同,这直接导致找错了桶。
小结:最好不要用可变的字段来计算哈希码!
性能
哈希码可能最终会在每次调用 equals 的时候计算,这可能正好发生在代码中性能极为关键的部分,所以考虑性能是很有意义的。相比之下 equals 的优化空间就非常小。
除非是使用了复杂的算法,或者使用的字段非常非常多,组合他们哈希码的计算成本可以忽略不计,因为这不可避免。但是应该考虑是否所有字段都需要包含在计算中!尤其应该以审视的眼光来看待集合,例如计算列表和集合中所有元素的哈希码。需要根据不同的情况来考虑是否需要它们参与计算。
如果性能是关键,使用 Object.hash 就可能不是最好的选择,因为它会为可变参数创建数组。
一般的优化原则是:谨慎处理!使用一个公共哈希算法的,可能需要放弃集合,并在分析可能的改进之后进行优化。
碰撞
如果只关注性能,下面这个实例怎么样?
@Override
public int hashCode() {
return 0;
}
毫无疑问,它很快。而且相等的对象会有相同的哈希码,这也让我们觉得不错。还有个亮点,它不涉及可变的字段!
但是,想想我们提到的桶是什么?这种情况下所有实例会被装进同一个桶中!通常这会导致使用一个链表来容纳所有元素,这样的性能太糟糕了——比如,每次执行 contains 都会对列表进行线性扫描。
因此,我们得让每个桶里的内容尽可能的少!一个即使对非常相似的对象计算的哈希码也大不相同的算法,会是一个不错的开始。
如何取得,一定程度上取决于选择的字段。我们用于计算的细节,更多时候是为了生成不同的哈希码。注意,这与我们对性能的想法完全相反。结果很有趣,用太多或者太少字段都会导致性能不佳。
防止碰撞的算法是哈希算法的另一部分。
计算哈希
计算字段的哈希码最简单的办法就是直接调用这个字段的 `hashCode`。可以手工来进行合并。一个公共算法是从任意的某个数开始,让它与另一个数(通常是一个小素数)相乘,再加上一个字段的哈希码,然后重复:
int prime = 31;
int result = 1;
result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
return result;
这有可能造成溢出,但这不是什么大问题,因为在 Java 中不会引发异常。
注意,如果输入数据有着特定的模式,最好的哈希算法都可能出现异常频繁的碰撞。举个简单的例子,假设我们用一个点的 x 坐标和 y 坐标来计算哈希。一开始不太糟,直到我们发现这样一条直线上的点:f(x) = -x,这些点的 x + y = 0。就会发生大量的碰撞!
不过话说回来:如果没有分析说明一个公共算法不正确,就大胆的使用。
总结
我们已经了解,计算哈希码就像是把某个事物精简成等价的整数:相等的对象必须有相同的哈希码。同时为了保证较好的性能,拥有同一个哈希码的的对象越少越好。
这意味着如果覆写了 equals 就必须覆写 hashCode。
实现 hashCode 时:
与 equals 使用相同的字段(或者是这些字段的子集)。
最好不要包含可变字段。
考虑不去调用集合的 hashCode。
使用公共算法,除非输入数据的模式严重影响这个算法的效果。
记住,hashCode 与效果相关,所以不要在上面浪费过多精力,除非分析结果指出优化的必要性。
如何正确实现 Java 中的 HashCode的更多相关文章
- 浅谈Java中的hashcode方法
哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: 1 public native int hashCode(); 根据 ...
- java中的hashcode
hashcode的作用 对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode.在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括 ...
- 千万不要误用 java 中的 HashCode 方法
刚才debug追堆栈的时候发现一个很奇怪的问题 我用IE8和Google的浏览器访问同一个地址 Action的 scope="session" 也设置了 而且两个浏览器提交的参数m ...
- 【转】浅谈Java中的hashcode方法(这个demo可以多看看)
浅谈Java中的hashcode方法 哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native i ...
- K:java中的hashCode和equals方法
hashCode和equals方法是Object类的相关方法,而所有的类都是直接或间接的继承于Object类而存在的,为此,所有的类中都存在着hashCode和equals.通过翻看Object类 ...
- 【转】浅谈Java中的hashcode方法
哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 根据这个 ...
- Java中的hashcode方法
一.hashCode方法的作用 对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode.在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列 ...
- java 中的 hashcode
在Java的Object类中有一个方法: public native int hashCode(); 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没 ...
- 浅谈Java中的hashcode方法(转)
原文链接:http://www.cnblogs.com/dolphin0520/p/3681042.html 浅谈Java中的hashcode方法 哈希表这个数据结构想必大多数人都不陌生,而且在很多地 ...
随机推荐
- MySQL的BLOB类型(解决mysql不支持mb4编码的时候存储emoji表情问题)
今天在存储emoji表情的时候,发现无法存储,mysql版本太低也没办法使用uft8mb4格式编码,只能将数据字段设置为blob BLOB是一个二进制大对象,可以容纳可变数量的数据.有4种BLOB类型 ...
- 【转载】Java Restful API 文档生成工具 smart-doc
谁说生成api文档就必须要定义注解? 谁说生成接口请求和返回示例必须要在线? 用代码去探路,不断尝试更多文档交付的可能性. 如果代码有生命,为什么不换种方式和它对话! 一.背景 没有背景.就自己做自己 ...
- 201671010142 java类与对象的定义及使用
通过这章学习,了解到面向对象程序设计.面向对象的程序设计是有对象组成的,每个对象对用户公开的特定功能部分和隐藏的实现部分.有类构造对象的过程称为创建类的实例.实现封装的关键在于绝对不能让类中的方法直接 ...
- swift3笔记
备注:这里只是个人的观点,有的地方也是copy,多多指教,个人笔记,有侵犯你们版权的地方还望海涵!!! 个人觉得swift和OC最大的区别就是增加了许多现代化的东西,java里面的闭包,private ...
- Java基础-访问修饰符
访问修饰符 default (即缺省,什么也不写): 在同一包内可见,不使用任何修饰符.使用对象:类.接口.变量.方法. private : 在同一类内可见.使用对象:变量.方法. 注意:不能修饰类( ...
- firstPage
自己写的科比的一个简单介绍的网页,画面,布局还是太垃圾了,图片放大缩小标签不知道,简直难受. <!DOCTYPE html><html> <head> <me ...
- 如何ASP.NET Core Razor中处理Ajax请求[转载]
在ASP.NET Core Razor(以下简称Razor)刚出来的时候,看了一下官方的文档,一直没怎么用过. 今天闲来无事,准备用Rozor做个项目熟练下,结果写第一个页面就卡住了..折腾半天才搞好 ...
- Exception,标准异常总结
常见异常种类:
- SQLite相关异常
SQLite的异常大多都和异常IO操作有关,这类异常通常在debug测试的时候难以发现通常和用户的操作有关系,根据我遇到的包括以下几种: 1.No transaction is active 这种情况 ...
- 蓝牙协议分析(8)_BLE安全机制之白名单
1. 前言 在万物联网的时代,安全问题将会受到非常严峻的挑战(相应地,也会获得最大的关注度),因为我们身边的每一个IOT设备,都是一个处于封印状态的天眼,随时都有被开启的危险.想想下面的场景吧: 凌晨 ...