如何正确实现 Java 中的 HashCode

相等 和 Hash Code

从一般角度来看,Equality 是不错的,但是 hash code 更则具技巧性。如果我们在 hash code上多下点功夫,我们就能了解到 hash code 就是用在细微处去提升性能的。

大部分的数据结构使用equals去检查是否他们包含一个元素。例如:

1
2
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 有个很简单的实现:

1
2
3
4
@Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}

通过计算相关字段的哈希码,再把这些哈希码组合起来得到 person 的哈希码。它们用 Object 的工具函数 hash 来参与计算。

选择字段

然而什么字段才是相关的?这些要求有助于回答这个问题:如果相等的对象必须有相同的哈希码,那么在计算哈希码的时候就不应该使用那些不用于相等性检查的字段。(否则,如果两个对象只有那些字段不同的话,它们会相等但哈希码不同。)

所以用于计算哈希码的那些字段应该是用于相等性比较的那些字段的子集。默认情况下,它们会使用相同的字段,但有几个细节需要考虑。

一致性

第一是一致性要求。它应该经过非常严格的计算。如果有字段产生了变化,哈希码也应该允许变化(对于可变类来说,这往往是不可避免的),依赖哈希的数据结构并未准备应付这种情况。

正如我们在上面看到的那样,哈希码用于确定一个元素的桶,但是如果哈希相关的字段发生变化,并不会立即重新计算哈希码,而且内部的数组也不会更新。

这就意味着,再对一个相等的对象甚至同一个对象的查询会失败!这个数据结构会计算当前的哈希码,这个哈希码与实例存入时的哈希码并不相同,这直接导致找错了桶。

小结:最好不要用可变的字段来计算哈希码!

性能

哈希码可能最终会在每次调用 equals 的时候计算,这可能正好发生在代码中性能极为关键的部分,所以考虑性能是很有意义的。相比之下 equals 的优化空间就非常小。

除非是使用了复杂的算法,或者使用的字段非常非常多,组合他们哈希码的计算成本可以忽略不计,因为这不可避免。但是应该考虑是否所有字段都需要包含在计算中!尤其应该以审视的眼光来看待集合,例如计算列表和集合中所有元素的哈希码。需要根据不同的情况来考虑是否需要它们参与计算。

如果性能是关键,使用 Object.hash 就可能不是最好的选择,因为它会为可变参数创建数组。

一般的优化原则是:谨慎处理!使用一个公共哈希算法的,可能需要放弃集合,并在分析可能的改进之后进行优化。

碰撞

如果只关注性能,下面这个实例怎么样?

1
2
3
4
@Override
public int hashCode() {
return 0;
}

毫无疑问,它很快。而且相等的对象会有相同的哈希码,这也让我们觉得不错。还有个亮点,它不涉及可变的字段!

但是,想想我们提到的桶是什么?这种情况下所有实例会被装进同一个桶中!通常这会导致使用一个链表来容纳所有元素,这样的性能太糟糕了——比如,每次执行 contains 都会对列表进行线性扫描。

因此,我们得让每个桶里的内容尽可能的少!一个即使对非常相似的对象计算的哈希码也大不相同的算法,会是一个不错的开始。

如何取得,一定程度上取决于选择的字段。我们用于计算的细节,更多时候是为了生成不同的哈希码。注意,这与我们对性能的想法完全相反。结果很有趣,用太多或者太少字段都会导致性能不佳。

防止碰撞的算法是哈希算法的另一部分。

计算哈希

计算字段的哈希码最简单的办法就是直接调用这个字段的 `hashCode`。可以手工来进行合并。一个公共算法是从任意的某个数开始,让它与另一个数(通常是一个小素数)相乘,再加上一个字段的哈希码,然后重复:

1
2
3
4
5
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。就会发生大量的碰撞!

转载于:https://www.cnblogs.com/frankyou/p/9848681.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/396372.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一亿小目标成就_成就卓越的一种方式:自我选择

一亿小目标成就by Prosper Otemuyiwa通过Prosper Otemuyiwa 成就卓越的一种方式&#xff1a;自我选择 (One way to Greatness: Pick Yourself) I’ve heard many people say this: “I want to be great”, but most people only just have wild thoughts & imaginations …

java操作文件爱女_Java的IO操作---File类

目标1)掌握File类作用2)可以使用file类中方法对文件进行读写操作。File类唯一与文件有关的类。使用file类可进行创建或删除操作&#xff0c;要想使用File类&#xff0c;首先观察File类的构造方法。public File(String pathname);实例化File类的时候&#xff0c;必须设置好路径。…

openssl创建私有ca

openssl创建私有ca1.ssl大概内容PKI&#xff1a;公钥基础设施结构CA&#xff1a;证书权威机构&#xff0c;PKI的核心CRL&#xff1a;证书吊销列表,使用证书之前需要检测证书有效性证书存储格式常见的X509格式包含内容 公钥有效期限证书的合法拥有人证书该如何使用CA的信息CA签名…

查询显示注释_SQL的简单查询

1.基本的查询语句-- *代表查询所有的列select * from <表名>;distinct表示列中不包括重复的值&#xff0c;例如select distinct 姓名&#xff1b;如果是select distinct 姓名,学号&#xff1b;则表示姓名和学号都重复的值才会显示。as为列设定别名&#xff0c;例如select…

【AC自动机】【数据结构】【树】【Aho-Corasick automation】AC自动机理解(入门)...

引入 我们首先提出一个问题&#xff1a; 给出n个串每个串的长度≤m 然后给出一个长度为k的串&#xff0c;询问前n个串中有多少个是匹配成了的 暴力搜索 这题不是sb题目吗&#xff1f; 随随便便O(kmn)跑过。 。。。。 n10000 m50 k1000000 。。。。 好吧——我们用AC自动…

域控dns无法解析域控_域注册商,DNS和托管

域控dns无法解析域控by ᴋɪʀʙʏ ᴋᴏʜʟᴍᴏʀɢᴇɴ由ᴋɪʀʙʏᴋᴏʜʟᴍᴏʀɢᴇɴ 域名注册商&#xff0c;DNS和托管 (Domain registrars, DNS, and hosting) 如何正确设置网站 (How to set up your website the right way) It took me a while to set up the infras…

java 栈空间_初学JAVA——栈空间堆空间的理解

1.Person pangzi; //这是在“开拓空间”于栈空间pangzinew Person(); //这是赋值于堆空间上两步就是在做与空间对应的事。2.值类型直接存入栈空间&#xff0c;如AF&#xff0c;引用类型存入堆空间&#xff0c;在栈空间存有“索引地址”&#xff0c;如当需要B时&#xff0…

二进制安装kubernetes v1.11.2 (第八章 kube-apiserver 部署)

继续上一章部署。 八、部署kube-apiserver组件 使用第七章的haproxy和keepalived部署的高可用集群提供的VIP&#xff1a;${MASTER_VIP} 8.1 下载二进制文件&#xff0c;参考 第三章  8.2 创建 kubernetes 证书和私钥 source /opt/k8s/bin/environment.sh cat > kubernetes-…

element手机验证格式_vue封装 element-ui form表单验证 正则匹配手机号 自定义校验表格内容...

效果image.png在methods中//检查手机号isCellPhone(val) {if (!/^1(3|4|5|6|7|8)\d{9}$/.test(val)) {return false;} else {return true;}}在template中v-model"forgetForm.phone"type"text"auto-complete"off"placeholder"请输入你的手机…

multi-mechanize error: can not find test script: v_user.py问题

从github上下载&#xff0c;安装multi-mechanize&#xff0c;新建工程&#xff0c;运行工程报错。 环境&#xff1a; win7-x64, python 2.7 multi-mechanize can not find test script: v_user.py 查看了github上的工程&#xff0c;项目无人维护&#xff0c;这个问题2016年11月…

@RequestMapping 用法详解之地址映射

引言&#xff1a; 前段时间项目中用到了RESTful模式来开发程序&#xff0c;但是当用POST、PUT模式提交数据时&#xff0c;发现服务器端接受不到提交的数据&#xff08;服务器端参数绑定 没有加任何注解&#xff09;&#xff0c;查看了提交方式为application/json&#xff0c; 而…

我的第一个网页 代码_我在免费代码营的第一个月

我的第一个网页 代码by Elliott McNary埃利奥特麦克纳里(Elliott McNary) 我在免费代码营的第一个月 (My First Month At Free Code Camp) I wanted to build an app that would help artists to make more money.我想开发一个可以帮助艺术家赚更多钱的应用。 I had a clear …

java pem rsa_如何从java中的pfx文件/ pem文件中获取RSA公钥的指数和模数值

I want to extract information about RSA Public Key from the pfx file using java.我有一个pfx文件并转换为x509 Pem文件 . 从pem文件&#xff0c;在终端中使用以下命令&#xff1a;openssl x509 -in file.pem -text我能够查看公钥指数和模数值主题公钥信息&#xff1a;Publ…

jmeter+maven+jenkins自动化接口测试(下)

mavenjmeter已经写好了&#xff0c;可以通过maven来执行jmeter的接口测试脚本&#xff0c;怎样实现定时执行测试并发送报告邮件就需要通过jenkins了&#xff08;jmeter或者testng也可以结合不同的邮件jar包来发送邮件&#xff0c;这里使用jenkins&#xff09; 安装jenkins笔记有…

在使用angularjs过程,ng-repeat中track by的作用

转载&#xff1a;http://segmentfault.com/q/1010000000405730<div ng-repeat"links in slides"> <div ng-repeat"link in links track by $index">link.name</div></div>Error: [ngRepeat:dupes]这个出错提示具体到题主的情况…

java判断读到末尾_IO流如何判断读取到了流的结尾,程序中以-1来判断,是流中写入一个EOF表示流结束吗,底层实现呢?...

-1不是流中写入的数据。read()方法返回的数据都是unsigned byte&#xff0c;即是[0,255]。底层实现有很多&#xff0c;比如socket IO和文件IO&#xff0c;甚至你自己也可以实现。——————————————————————给两个类的代码给你看看&#xff0c;理解一下这个东…

结束书

by William Countiss威廉Countiss 结束书 (Closing the Book on Closures) JavaScript closures are an important, but notoriously confusing concept. There’s no escaping it — if you want to grow as a developer, you need to understand what closures are and how …

java激励_激励干个人java的不足之处

1.你需要精通面向对象分析与设计(OOA/OOD)、涉及模式(GOF&#xff0c;J2EEDP)以及综合模式。你应该十分了解UML&#xff0c;尤其是class&#xff0c;object&#xff0c;interaction以及statediagrams。2.你需要学习JAVA语言的基础知识以及它的核心类库(collections&#xff0c;…

Bioconductor软件安装与升级

1 安装工具Bioc的软件包不能使用直接install.packages函数&#xff0c;它有自己的安装工具&#xff0c;使用下面的代码&#xff1a; source("https://bioconductor.org/biocLite.R")biocLite() 上面第二个语句将安装Bioconductor一些基础软件包&#xff0c;包括BiocI…

Laravel Kernel引导流程分析

Laravel Kernel引导流程分析 代码展示 protected function sendRequestThroughRouter($request) {# $this->app->instance(request, $request);# Facade::clearResolvedInstance(request);// 主要是这句代码$this->bootstrap();# return (new Pipeline($this->app)…