关于 hashCode() 你需要了解的 3 件事
(点击上方公众号,可快速关注)
原文:eclipsesource
译文:ImportNew - 南半球
链接:http://www.importnew.com/16517.html
在 Java 中,每一个对象都有一个容易理解但是仍然有时候被遗忘或者被误用的 hashCode 方法。这里有3件事情要时刻牢记以避免常见的陷阱。
一个对象的哈希码允许算法和数据结构将对象放入隔间,就象打印机类型案件中的字母类型。打印机将所有的“A”类型放到一个房间,它寻找这个“A”的时候就只需要在这个房间进行寻找。这种简单的系统让他在未排序的抽屉中寻找类型的时候更快。这也是基于哈希的集合的想法,例如 HashMap 和 HashSet。
为了使你的类与其他基于哈希的集合或其他依赖哈希码的算法一起正常工作,所有 hashCode 的实现必须遵守一个简单的契约。
hashCode 契约
这个契约在 hashCode 方法的 JavaDoc 中进行了阐述。它可以大致的归纳为下面几点:
在一个运行的进程中,相等的对象必须要有相同的哈希码
请注意这并不意味着以下常见的误解:
不相等的对象一定有着不同的哈希码——错!
有同一个哈希值的对象一定相等——错!
这个契约允许不同的对象共享相同的哈希码,例如根据上图中的的描述,“A”和“μ”对象的哈希值就一样。在数学术语中,从对象到哈希码的映射不一定为内射或者双射。这是显而易见的,因为可能的不同对象的数量经常比可能的哈希吗的数量 (2^32)更大。
编辑:在早期的版本中,我错误的认为哈希码的映射一定属于内射,但是不一定是双射,这显然是错的。感谢 Lucian 指出这个错误。
这个约定直接导致了第一个规则:
1. 无论你何时实现 equals 方法,你必须同时实现 hashCode 方法
如果你不这样做,你将会带来损坏的对象。为什么?一个对象的 hashCode 方法需要与 equals 方法考虑同样的域。通过重写 equals 方法,你将申明一些对象与其他对象相等,但是原始的 hashCode 方法将所有的对象看做是不同的。所以你将会有不同哈希码的相同对象。例如,在 HashMap 中调用 contains 方法将会返回 false,即使这个对象已经被添加。
怎样写一个好的 hashCode 方法不在这篇文章的范围内,在 Joshua Bloch 很受欢迎的书《Effective Java》中被很好的阐释,Java 开发人员的书架上不应缺少这本书。
【你的项目需要专业意见吗?我们的 Developer Support 会为你解决问题。|在我们的 Software Craftsmanship 页面上寻找关于怎样编写简洁代码的更多提示。】
为了安全起见,让 Eclipse IDE 一次产生 equals 和 hashCode 方法: Source > Generate hashCode() and equals()….
为了保护你自己,你还可以配置 Eclipse 来检测实现了 equals 方法但是没有实现 hashCode 方法的类,并显示错误。不幸的是,此选项默认是指为“忽略”:Preferences > Java >Compiler > Errors/Warnings,然后用快速筛选器来搜索“hashcode”:
更新:正如 laurent 指出,equalsverifier 是一个强大的工具,它用来验证 hashCode 和 equals 方法的约定。您应该考虑在您的单元测试中使用它。
哈希码冲突
任何时候,两个不同对象有相同的哈希码,我们称之为冲突。冲突不要紧,它只是意味着有多个对象在同一个空间里,所以 HashMap 会再检查一遍来找正确的对象。大量的冲突将会降低系统的性能,但是它们不会导致错误的结果。
但是如果你误认为哈希码是一个对象唯一的句柄,例如使用它作为Map的key,你有时会得到错误的对象。因为虽然冲突很罕见,但他们是不可避免的。例如,字符“Aa”和“BB”产生相同的哈希码:2112。因此:
2. 永远不要把哈希码误用作一个key
你可能会反对,不像打印机的类型例子,在 Java 中,有 4,294,967,296 的空间(2^32 个可能的整型值)。40亿的插槽,发生冲突似乎是极不可能的对吗?
事实证明它不是不太可能。这是令人惊讶的冲突:请想象一下在一个房间里有 23 个随机的人。你觉得两个人是同一天生日的几率有多大 ?很低,因为一年有 365 天吗?事实上,几率是 50% 左右!50 个人是保守的估计。这个现象称为生日悖论。应用到哈希码,这意味着在 77163 个不同的对象中,有 50% 的可能性发生冲突–假设你有一个理想的哈希的函数,均匀地把对象分布在所有可用的空间里面。
例如:
安然公司的电子邮件集包含 520,924 封电子邮件。计算电子邮件内容字符串的哈希码时,我发现 50 对(甚至是 2 个三元组)不同的电子邮件有着相同的哈希码。对于五十万个字符串,这是一个很好的结果。但是这里的信息是:如果你有很多数据元素,冲突就会发生。如果你正在使用哈希码作为 key,你不会立即注意到你的错误。但是少数人会收到错误的邮件。
哈希码可变
最后,在哈希码的契约中,有一个很重要的细节是相当让人吃惊的:hashCode 并不保证在不同的应用执行中得到相同的结果。让我们看一看 Java 文档:
在一次 Java 应用的执行中,对于同一个对象,hashCode 方法必须始终返回相同的整数,但这整数不反映对象是否被修改(equals 比较)的信息。同一个应用的不同执行,该整数不必保持一致。
事实上,这是不常见的,一些类库中的类甚至指定它们用于计算哈希码的精确公式(例如字符串)。对于这些类,哈希码总是会相同。虽然大部分的哈希码的实现提供稳定的值,但你不能依赖于这一点。正如这篇文章指出的,有些类库在不同进程中会返回不同的哈希值,这有的时候会让人困惑。谷歌的 Protocol Buffers 就是一个例子。
因此,你不应该在分布式应用程序中使用哈希码。一个远程对象可能与本地对象有不同的哈希码,即使这两个对象是相等的。
3. 在分布式应用中不要使用哈希码
此外,你应该意识到从一个版本到另一个版本哈希码的功能实现可能会更改。因此您的代码不应该依赖于任何特定的哈希码值。例如,你不应该使用哈希码来持久化状态。下次你运行程序的时候,“相同”对象的哈希码可能不同。
最好的建议可能是:完全不使用哈希码,除非你自己创造了基于哈希的算法。
一种替代方法:SHA1
你可能知道加密的哈希码 SHA1 有时被用来标识对象(例如,git这样做)。这也是不安全吗?不。SHA1 使用 160 位密钥,这使得冲突几乎是不可能的。即使有很多对象,在这个空间发生冲突的几率远远低于一颗流星撞到你正在执行程序的电脑的几率。这篇文章对冲突的概率作了很好的概述。
关于 hashCode() 你需要了解的 3 件事的更多相关文章
- 【转】关于 hashCode() 你需要了解的 3 件事
在 Java 中,每一个对象都有一个容易理解但是仍然有时候被遗忘或者被误用的 hashCode 方法.这里有3件事情要时刻牢记以避免常见的陷阱. 一个对象的哈希码允许算法和数据结构将对象放入隔间,就象 ...
- 关于 Java Collections API 您不知道的 5 件事,第 1 部分
定制和扩展 Java Collections Java™ Collections API 远不止是数组的替代品,虽然一开始这样用也不错.Ted Neward 提供了关于用 Collections 做更 ...
- 关于 Java Collections API 您不知道的 5 件事--转
第 1 部分 http://www.ibm.com/developerworks/cn/java/j-5things2.html 对于很多 Java 开发人员来说,Java Collections A ...
- 做一个 App 前需要考虑的几件事
做一个 App 前需要考虑的几件事 来源:limboy的博客 随着工具链的完善,语言的升级以及各种优质教程的涌现,做一个 App 的成本也越来越低了.尽管如此,有些事情最好前期就做起来,避免当 ...
- 【转载】在IT界取得成功应该知道的10件事
在IT界取得成功应该知道的10件事 2011-08-11 13:31:30 分类: 项目管理 导读:前面大多数文章都是Jack Wallen写的,这是他的新作,看来要成为NB程序员还要不停的自我总结 ...
- 安装完CentOS 7 后必做的七件事
CentOS是最多人用来运行服务器的 Linux 版本,最新版本是 CentOS 7.当你兴趣勃勃地在一台主机或 VPS 上安装 CentOS 7 后,首要的工作肯定是加强它的安全性,以下列出的七件事 ...
- A/B 测试之前必须要了解的 10 件事
如今,转化率优化(CRO)已是营销人员必须具备的技能,并且与 ROI 直接挂钩.但是在优化网页的转化率方面又有太多因素要考量,如果你已经不堪其忧,请专心做一件事-- A/B 测试. A/B测试,即你设 ...
- 关于Promise:你可能不知道的6件事
FROM ME : 文章介绍了6个Promise的知识点: 1.then() 返回一个 forked Promise(分叉的 Promise):返回的有两种情况: 2.回调函数应该传递结果:在 pro ...
- Ubuntu 16.04 LTS安装好需要设置的15件事(喜欢新版本)
看到这篇文章说明你已经从老版本升级到 Ubuntu 16.04 或进行了全新安装,在安装好 Ubuntu 16.04 LTS 之后建议大家先做如下 15 件事.无论你是刚加入 Ubuntu 行列的新用 ...
随机推荐
- ExtJs 继承 和 插件 示例
Ext.ns('Ext.ux'); function btn(){ alert(this.id); }; var panel_plugs = {//定义插件 init : function(panel ...
- 一个响应式数据库框架SQLBrite,完美解决数据库和UI的同步更新!
相信小伙伴们在开发中或多或少都可能遇到过这样的问题:打开一个应用后,为了快速响应,先将数据库中的数据呈现给用户,然后再去网络上请求数据,请求成功之后将数据缓存至数据库,同时更新UI,但是我们经常会这样 ...
- myeclipse连接数据库遇到的几个问题
1:无效的SQL URL: //将获取的参数插入数据库 Connection conn=null; PreparedStatement stat=null; ...
- 关于JFace中的输入值(InputDialog)对话框类
格式: InputDialog(Shell parentShell, String dialogTitle, String dialogMessage, String initialValue, In ...
- 【linux操作命令】mysql
1.linux下启动mysql的命令: mysqladmin start /ect/init.d/mysql start (前面为mysql的安装路径) 2.linux下重启mysql的命令: mys ...
- JS+CSS简单实现DIV遮罩层显示隐藏【转藏】
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- RSA算法使用介绍
http://www.cnblogs.com/AloneSword/p/3326750.html RSA是目前最有影响力的公钥加密算法,该算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但那 ...
- 从CSDN转战于此
平时喜欢写博客,开始是再CSDN,但CSDN博客功能实在不是很好.听说博客园的博客功能很不错,今天就来到此地.实际上很早就注册了博客园,但博客今天才申请的. 博客园,来了!
- ios数据库FMDB
一.下载fmdb类库 二.添加libsqulite3.0.dylib 三.添加头文件#import "FMDB.h" 四.打开数据库 a.设置路径NSString *path = ...
- 12天学好C语言——记录我的C语言学习之路(Day 1)
12天学好C语言--记录我的C语言学习之路 Day 1: 刚刚入门C语言,那么肯定要先把什么是C语言和大家讲清楚,那么大家看下面一段程序(program 1.1): /*//program 1.1 ...