Java中hashcode的理解 css
以 java.lang.Object来理解,JVM每new一个Object,它都会将这个Object丢到一个Hash哈希表中去,这样的话,下次作 Object的比較或者取这个对象的时候,它会依据对象的hashcode再从Hash表中取这个对象。这样作的目的是提升取对象的效率。详细过程是这 样:
1.new Object(),JVM依据这个对象的Hashcode值,放入到相应的Hash表相应的Key上,假设不一样的对象确产生了一样的hash值,也就是发 生了Hash key一样致使冲突的状况,那么就在这个Hash key的地方产生一个链表,将所有产生一样hashcode的对象放到这个单链表上去,串在一块儿。
2.比較两个对象的时候,首先依据他们的 hashcode去hash表中找他的对象,当两个对象的hashcode一样,那么就是说他们这两个对象放在Hash表中的同一个key上,那么他们一 定在这个key上的链表上。java
那么此时就仅仅能依据Object的equal方法来比較这个对象是否equal。当两个对象的hashcode不一样的话,确定 他们不能equal.算法
============================================================ 编程
java.lang.Object中对hashCode的约定:数组
有一个概念要牢记。两个相等对象的equals方法必定为true, 但两个hashcode相等的对象不必定是相等的对象。缓存
因此hashcode相等仅仅能保证两个对象在一个HASH表里的同一条HASH链上,继而经过equals方法才干肯定是否是同一对象,假设结果为true, 则以为是同一对象在插入。不然以为是不一样对象继续插入。安全
Object的代码:
public String toString () {
return this.getClass().getName() + “@” + Integer.toHexString(this.hashCode());
}markdown
public boolean equals (Object o) {
return this == o;
}数据结构
public native int hashCode();架构
package com.tools;
import java.util.ArrayList;
public class HashCodeMeaning {
public static void main(String[] args) {
ArrayList list = new ArrayList();
int numberExist=0;
//证实hashcode的值不是内存地址 for (int i = 0; i < 10000; i++) { Object obj=new Object(); if (list.contains(obj.toString())) { System.out.println(obj.toString() +" exists in the list. "+ i); numberExist++; } else { list.add(obj.toString()); } } System.out.println("repetition number:"+numberExist); System.out.println("list size:"+list.size()); //证实内存地址是不一样的。 numberExist=0; list.clear(); for (int i = 0; i < 10000; i++) { Object obj=new Object(); if (list.contains(obj)) { System.out.println(obj +" exists in the list. "+ i); numberExist++; } else { list.add(obj); } } System.out.println("repetition number:"+numberExist); System.out.println("list size:"+list.size()); }
}
==============================
==============================
Hash表数据结构常识:
1、哈希表基于数组。
2、缺点:基于数组的。数组建立后难以扩展。
某些哈希表被基本填满时。性能降低得很是严重。
3、没有一种简便得方法可以以不论什么一种顺序遍历表中数据项。
4、假设不需要有序遍历数据,而且可以提早预測数据量的大小。那么哈希表在速度和易用性方面是无与伦比的。
1、为何HashCode对于对象是如此的重要:
一个对象的HashCode就是一个简单的Hash算法的实现,尽管它和那些真正的复杂的Hash算法相比还不能叫真正的算法,它怎样实现它,不只仅是程序猿的编程水平问题,
而是关系到你的对象在存取是性能的很是重要的关系.有可能,不一样的HashCode可能会使你的对象存取产生,成百上千倍的性能差异.
先 来看一下。在JAVA中两个重要的数据结构:HashMap和Hashtable。尽管它们有很是大的差异。如继承关系不一样,对value的约束条件(是否 赞成null)不一样,以及线程安全性等有着特定的差异。但从实现原理上来讲,它们是一致的.因此,咱们仅仅以Hashtable来讲明:
在java中。存取数据的性能,通常来讲固然是首推数组,但是在数据量稍大的容器选择中,Hashtable将有比数据性能更高的查询速度.详细缘由看如下的内容.
Hashtable在存储数据时。通常先将该对象的HashCode和0x7FFFFFFF作与操做,因为一个对象的HashCode可以为负数,这样操做后可以保证它为一个正整数.而后以Hashtable的长度取模,获得该对象在Hashtable中的索引.
index = (o.hashCode() & 0x7FFFFFFF)%hs.length;
这 个对象就会直接放在Hashtable的每index位置,对于写入,这和数据同样,把一个对象放在当中的第index位置,但假设是查询,通过一样的算 法,Hashtable可以直接从第index取得这个对象。而数组却要作循环比較.因此对于数据量稍大时。Hashtable的查询比数据具备更高的性 能.
既然一个对象可以依据HashCode直接定位它在Hashtable中的位置,那么为何Hashtable还要用key来作映射呢?这就是关系Hashtable性能问题的最重要的问题:Hash冲突.
常 见的Hash冲突是不一样对象终于产生了一样的索引。而一种很是甚至绝对少见的Hash冲突是,假设一组对象的个数大过了int范围。而HashCode的 长度仅仅能在int范围中,因此确定要有同一组的元素有一样的HashCode,这样无论怎样他们都会有一样的索引.固然这样的极端的状况是极少见的。可以暂 不考虑,但是对于同的HashCode通过取模,则会产中一样的索引,或者不一样的对象却具备一样的HashCode,固然具备一样的索引.
因此对于索引一样的对象,在该index位置存放了多个值,这些值要想能正确区分。就要依靠key来识别.
事 实上一个设计各好的HashTable,通常来讲会比較平均地分布每个元素。因为Hashtable的长度老是比实际元素的个数按必定比例进行自增(装填 因子通常为0.75)左右,这样大多数的索引位置仅仅有一个对象,而很是少的位置会有几个元素.因此Hashtable中的每个位置存放的是一个链表,对于仅仅 有一个对象是位置。链表仅仅有一个首节点(Entry)。Entry的next为null.而后有hashCode。key,value属性保存了该位置的 对象的HashCode,key和value(对象自己)。假设有一样索引的对象进来则会进入链表的下一个节点.假设同一个索引中有多个对象,依据 HashCode和key可以在该链表中找到一个和查询的key相匹配的对象.
从上面我看可以看到,对于HashMap和Hashtable的 存取性能有重大影响的首先是应该使该数据结构中的元素尽可能大可能具备不一样的HashCode。尽管这并不能保证不一样的HashCode产生不一样的 index,但一样的HashCode必定产生一样的index,从而影响产生Hash冲突.
对于一个象,假设具备很是多属性,把所有属性都參与散列,显然是一种笨拙的设计.因为对象的HashCode()方法差点儿无所不在地被本身主动调用。如equals比較。假设太多的对象參与了散列.
那么需要的操做常数时间将会添加很是大.因此,挑选哪些属性參与散列绝对是一个编程水平的问题.
从实现来讲,通常的HashCode方法会这样:
return Attribute1.HashCode() Attribute1.HashCode()..[ super.HashCode()],咱们知道,每次调用这种方法,都要又一次对方法内的參与散列的对象又一次计算一次它们的HashCode的运算。假设一 个对象的属性没有改变。仍然要每次都进行计算。因此假设设置一个标记来缓存当前的散列码。仅仅要当參与散列的对象改变时才又一次计算。不然调用缓存的 hashCode,这可以从很是大程度上提升性能.
默认的实现是将对象内部地址转化为整数做为HashCode。这固然能保证每个对象具备不一样的HasCode。因为不一样的对象内部地址确定不一样(废话)。但java语言并不能让程序猿获取对象内部地址。因此,让每个对象产生不一样的HashCode有着很是多可研究的技术.
如 果从多个属性中採样出能具备平均分布的hashCode的属性,这是一个性能和多样性相矛盾的地方。假设所有属性都參与散列,固然hashCode的多样 性将大大提升。但牺牲了性能,而假设仅仅能少许的属性採样散列,极端状况会产生大量的散列冲突,如对”人”的属性中,假设用性别而不是姓名或出生日期。那将 仅仅有两个或几个可选的hashcode值,将产生一半以上的散列冲突.因此假设可能的条件下。专门产生一个序列用来生成HashCode将是一个好的选择 (固然产生序列的性能要比所有属性參与散列的性能高的状况下才行,不然还不如直接用所有属性散列).
怎样对HashCode的性能和多样性求得一个平衡,可以參考相关算法设计的书,事实上并不必定要求很是的优秀,仅仅要能尽最大可能减小散列值的汇集.重要的是咱们应该记得HashCode对于咱们的程序性能有着重要的影响。在程序设计时应该时时加以注意.
请记住:假设你想有效的使用HashMap。你就必须重写在其的HashCode()。
还有两条重写HashCode()的原则:
不 必对每个不一样的对象都产生一个惟一的hashcode,仅仅要你的HashCode方法使get()可以获得put()放进去的内容就可以了。
即“不为一原 则”。生成hashcode的算法尽可能使hashcode的值分散一些, 不要很是多hashcode都集中在一个范围内。这样有利于提升HashMap的性能。
即“分散原则”。
至于第二条原则的详细缘由,有兴趣者可以參考 Bruce Eckel的《Thinking in Java》,
在那里有对HashMap内部实现原理的介绍,这里就不赘述了。
掌握 了这两条原则。你就可以用好HashMap编写本身的程序了。
不知道你们注意没有。 java.lang.Object中提供的三个方法:clone(),equals()和hashCode()尽管很是典型,但在很是多状况下都不可以适用。 它们仅仅是简单的由对象的地址得出结果。
这就需要咱们在本身的程序中重写它们,事实上java类库中也重写了千千万万个这样的方法。利用面向对象的多态性—— 覆盖,Java的设计者很是优雅的构建了Java的结构,也更加体现了Java是一门纯OOP语言的特性。
Java提供的Collection和Map的功能是十分强大的。它们可以使你的程序实现方式更为灵活,运行效率更高。但愿本文可以对你们更好的使用HashMap有所帮助。
hashcode理论与实践:
有效和正肯定义hashCode()和equals()
每个Java对象都有hashCode()和 equals()方法。
不少类忽略(Override)这些方法的缺省实施,以在对象实例之间提供更深层次的语义可比性。
虽 然Java语言不直接支持关联数组。
可以使用不论什么对象做为一个索引的数组 – 但在根Object类中使用hashCode()方法明白表示指望普遍使用HashMap(及其前辈Hashtable)。理想状况下基于散列的容器提供 有效插入和有效检索;直接在对象模式中支持散列可以促进基于散列的容器的开发和使用。
定义对象的相等性
Object类有两种方法来推 断对象的标识:equals()和hashCode()。通常来讲,假设您忽略了当中一种,您必须同一时候忽略这两种,因为二者之间有必须维持的相当重要的关 系。特殊状况是依据equals() 方法。假设两个对象是相等的,它们必须有一样的hashCode()值(尽管这一般不是真的)。
特定类的equals()的语义在Implementer的左側定义;定义对特定类来讲equals()意味着什么是其设计工做的一部分。Object提供的缺省实施简单引用如下等式:
public boolean equals(Object obj) { return (this == obj); }
在 这样的缺省实施状况下,仅仅有它们引用真正同一个对象时这两个引用才是相等的。一样,Object提供的hashCode()的缺省实施经过将对象的内存地址 对映于一个整数值来生成。因为在某些架构上,地址空间大于int值的范围,两个不一样的对象有一样的hashCode()是可能的。假设您忽略了 hashCode(),您仍旧可以使用System.identityHashCode()方法来接入这类缺省值。
忽略 equals() – 简单实例
缺省状况下,equals()和hashCode()基于标识的实施是合理的,但对于某些类来讲,它们但愿放宽等式的定义。好比,Integer类定义equals() 与如下类似:
public boolean equals(Object obj) {
return (obj instanceof Integer
&& intValue() == ((Integer) obj).intValue());
}
在 这个定义中,仅仅有在包括一样的整数值的状况下这两个Integer对象是相等的。
结合将不可改动的Integer,这使得使用Integer做为 HashMap中的keyword是切实可行的。这样的基于值的Equal方法可以由Java类库中的所有原始封装类使用。如Integer、Float、 Character和Boolean以及String(假设两个String对象包括一样顺序的字符,那它们是相等的)。因为这些类都是不可改动的而且可 以实施hashCode()和equals()。它们都可以作为很是好的散列keyword。
为何忽略 equals()和hashCode()?
如 果Integer不忽略equals() 和 hashCode()状况又将怎样?假设咱们从未在HashMap或其余基于散列的集合中使用Integer做为keyword的话。什么也不会发生。但是,假设 咱们在HashMap中使用这类Integer对象做为keyword,咱们将不可以可靠地检索相关的值,除非咱们在get()调用中使用与put()调用中极其 类似的Integer实例。
这要求确保在咱们的整个程序中,仅仅能使用相应于特定整数值的Integer对象的一个实例。不用说,这样的方法极不方便而且错误 频频。
Object的interface contract要求假设依据 equals()两个对象是相等的,那么它们必须有一样的hashCode()值。
当其识别能力整个包括在equals()中时,为何咱们的根对象类需 要hashCode()?hashCode()方法纯粹用于提升效率。Java平台设计人员估计到了典型Java应用程序中基于散列的集合类 (Collection Class)的重要性–如Hashtable、HashMap和HashSet,而且使用equals()与不少对象进行比較在计算方面很是昂贵。使所 有Java对象都可以支持 hashCode()并结合使用基于散列的集合。可以实现有效的存储和检索。
实施equals()和hashCode()的需求
实施equals()和 hashCode()有一些限制,Object文件里列举出了这些限制。
特别是equals()方法必须显示如下属性:
Symmetry:两个引用,a和 b,a.equals(b) if and only if b.equals(a)
Reflexivity:所有非空引用, a.equals(a)
Transitivity:If a.equals(b) and b.equals(c), then a.equals(c)
Consistency with hashCode():两个相等的对象必须有一样的hashCode()值
Object 的规范中并无明白要求equals()和 hashCode() 必须一致 – 它们的结果在随后的调用中将是一样的。假设“不改变对象相等性比較中使用的不论什么信息。”这听起来象“计算的结果将不改变,除非实际状况如此。”这一模糊声 明一般解释为相等性和散列值计算应是对象的可肯定性功能,而不是其余。
对象相等性意味着什么?
人们很是easy知足Object类规范对 equals() 和 hashCode() 的要求。决定是否和怎样忽略equals()除了判断之外。还要求其余。
在简单的不可修值类中,如Integer(事实上是差点儿所有不可改动的类),选择 至关明显 – 相等性应基于基本对象状态的相等性。在Integer状况下,对象的惟一状态是主要的整数值。
对于可改动对象来讲,答案并不总 是如此清楚。
equals() 和hashCode() 是否应基于对象的标识(象缺省实施)或对象的状态(象Integer和String)?没有简单的答案 – 它取决于类的计划使用。
对于象List和Map这样的容器来讲。人们对此争论不已。Java类库中的大多数类,包括容器类,错误出现在依据对象状态来提供 equals()和hashCode()实施。
假设对象的hashCode()值可以基于其状态进行更改,那么当使用这类对象做为基于散列的集 合中的keyword时咱们必须注意。确保当它们用于做为散列keyword时,咱们并不一样意更改它们的状态。所有基于散列的集合假设,当对象的散列值用于做为集合中的关 键字时它不会改变。
假设当keyword在集合中时它的散列代码被更改,那么将产生一些不可预測和easy混淆的结果。
实践过程当中这一般不是问题 – 咱们并不经常使用象List这样的可改动对象作为HashMap中的keyword。
一个简单的可改动类的样例是Point,它依据状态来定义equals()和hashCode()。假设两个Point 对象引用一样的(x, y)座标。Point的散列值来源于x和y座标值的IEEE 754-bit表示,那么它们是相等的。
对 于比較复杂的类来讲,equals()和hashCode()的行为可能甚至受到superclass或interface的影响。好比,List接口要 求假设而且仅仅有还有一个对象是List,而且它们有一样顺序的一样的Elements(由Element上的Object.equals() 定义),List对象等于还有一个对象。
hashCode()的需求更特殊–list的hashCode()值必须符合如下计算:
hashCode = 1;
Iterator i = list.iterator();
while (i.hasNext()) {
Object obj = i.next();
hashCode = 31*hashCode + (obj==null ?
0 : obj.hashCode());
}
不只仅散列值取决于list的内容,而且还规定告终合各个Element的散列值的特殊算法。
(String类规定类似的算法用于计算String的散列值。
)
编写本身的equals()和hashCode()方法
忽 略缺省的equals()方法比較简单,但假设不违反对称(Symmetry)或传递性(Transitivity)需求,忽略已经忽略的 equals() 方法极其棘手。当忽略equals()时。您应该老是在equals()中包括一些Javadoc凝视。以帮助那些但愿可以正确扩展您的类的用户。
做为一个简单的样例,考虑如下类:
class A {
final B someNonNullField;
C someOtherField;
int someNonStateField;
}
咱们应怎样编写该类的equals()的方法?这样的方法适用于不少状况:
public boolean equals(Object other) {
// Not strictly necessary, but often a good optimization
if (this == other)
return true;
if (!(other instanceof A))
return false;
依据您使用的类。您可能但愿减小 superclass的equals()或hashCode()功能一部分计算能力。
对于原始字段来讲,在相关的封装类中有helper功能,可以帮助创 建散列值。如Float.floatToIntBits。
编写一个完美的equals()方法是不现实的。一般。当扩展一个自身忽略了 equals()的instantiable类时,忽略equals()是不切实际的,而且编写将被忽略的equals()方法(如在抽象类中)不一样于为 详细类编写equals()方法。关于实例以及说明的更详细信息请參阅Effective Java Programming Language Guide, Item 7 (參考资料) 。
有待改进?
将散列法构建到Java类库的根对象类中是一种很是明智的设计折衷方法 – 它使使用基于散列的容器变得如此简单和高效。但是,人们对Java类库中的散列算法和对象相等性的方法和实施提出了不少批评。java.util中基于散 列的容器很是方便和简便易用。但可能不适用于需要很是高性能的应用程序。尽管当中大部分将不会改变,但当您设计严重依赖于基于散列的容器效率的应用程序时 必须考虑这些因素,它们包括:
过小的散列范围。
使用int而不是long做为hashCode()的返回类型添加了散列冲突的概率。
糟糕的散列值分配。短strings和小型integers的散列值是它们本身的小整数,接近于其余“邻近”对象的散列值。一个循规导矩(Well-behaved)的散列函数将在该散列范围内更均匀地分配散列值。
无 定义的散列操做。
尽管某些类,如String和List。定义了将其Element的散列值结合到一个散列值中使用的散列算法。但语言规范不定义将多个对 象的散列值结合到新散列值中的不论什么批准的方法。咱们在前面编写本身的equals()和hashCode()方法中讨论的List、String或实例类 A使用的诀窍都很是easy,但算术上还远远不够完美。类库不提供不论什么散列算法的方便实施,它可以简化更先进的hashCode()实施的建立。
当扩 展已经忽略了equals()的 instantiable类时很是难编写equals()。
当扩展已经忽略了equals()的 instantiable类时,定义equals()的“显而易见的”方式都不能知足equals()方法的对称或传递性需求。这意味着当忽略 equals()时。您必须了解您正在扩展的类的结构和实施详细信息,甚至需要暴露基本类中的机密字段,它违反了面向对象的设计的原则。
结束语
通 过统必定义equals()和hashCode()。您可以提高类做为基于散列的集合中的keyword的使用性。有两种方法来定义对象的相等性和散列值:基于标 识,它是Object提供的缺省方法;基于状态,它要求忽略equals()和hashCode()。
当对象的状态更改时假设对象的散列值发生变化,确信 当状态做为散列keyword使用时您不一样意更更改其状态。
解析Java对象的equals()和hashCode()的使用:
在 Java语言中。equals()和hashCode()两个函数的使用是紧密配合的,你要是本身设计当中一个,就要设计另一个。
在多数状况 下,这两个函数是不用考虑的。直接使用它们的默认设计就可以了。
但是在一些状况下,这两个函数最好是本身设计,才干确保整个程序的正常运行。最多见的是当 一个对象被添加收集对象(collection object)时。这两个函数必须本身设计。更细化的定义是:假设你想将一个对象A放入还有一个收集对象B里,或者使用这个对象A为查找一个元对象在收集对 象B里位置的钥匙,并支持是否容纳。删除收集对象B里的元对象这样的操做,那么,equals()和hashCode()函数必须开发人员自定义。其余情 况下,这两个函数是不需要定义的。
equals():
它是用于进行两个对象的比較的,是对象内容的比較,固然也能用于进行对 象參阅值的比較。
什么是对象參阅值的比較?就是两个參阅变量的值得比較,咱们 都知道參阅变量的值事实上就是一个数字,这个数字可以当作是鉴别不一样对象的代号。两个对象參阅值的比較。就是两个数字的比較。两个代号的比較。这样的比較是默 认的对象比較方式,在Object这个对象中,这样的方式就已经设计好了。
因此你也不用本身来重写,浪费没必要要的时间。
对象内容的比較才是设计equals()的真正目的,Java语言对equals()的要求例如如下,这些要求是必须遵循的。不然,你就不应浪费时间:
对称性:假设x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
反射性:x.equals(x)必须返回是“true”。
类推性:假设x.equals(y)返回是“true”,而且y.equals(z)返回是“true”。那么z.equals(x)也应该返回是“true”。
还有一致性:假设x.equals(y)返回是“true”,仅仅要x和y内容一直不变,无论你反复x.equals(y)多少次。返回都是“true”。
不论什么状况下,x.equals(null)。永远返回是“false”;x.equals(和x不一样类型的对象)永远返回是“false”。
hashCode():
这 个函数返回的就是一个用来进行哈希操做的整型代号,请不要把这个代号和前面所说的參阅变量所表明的代号弄混了。后者不只仅是个代号还具备在内存中才查找对 象的位置的功能。hashCode()所返回的值是用来分类对象在一些特定的收集对象中的位置。这些对象是HashMap, Hashtable, HashSet,等等。这个函数和上面的equals()函数必须本身设计。用来协助HashMap, Hashtable, HashSet,等等对本身所收集的大量对象进行搜寻和定位。
这些收集对象到底怎样工做的,想象每个元对象hashCode是一个箱子的 编码,依照编码,每个元对象就是依据hashCode()提供的代号纳入相应的箱子里。所有的箱子加起来就是一个HashSet,HashMap,或 Hashtable对象,咱们需要寻找一个元对象时,先看它的代码,就是hashCode()返回的整型值,这样咱们找到它所在的箱子,而后在箱子里,每 个元对象都拿出来一个个和咱们要找的对象进行对照。假设两个对象的内容相等。咱们的搜寻也就结束。
这样的操做需要两个重要的信息。一是对象的 hashCode()。还有一个是对象内容对照的结果。
hashCode()的返回值和equals()的关系例如如下:
假设x.equals(y)返回“true”,那么x和y的hashCode()必须相等。
假设x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。
为 什么这两个规则是这样的。缘由事实上很是easy,拿HashSet来讲吧,HashSet可以拥有一个或不少其余的箱子,在同一个箱子中可以有一个 或不少其余的独特元对象(HashSet所容纳的必须是独特的元对象)。这个样例说明一个元对象可以和其余不一样的元对象拥有一样的hashCode。但是一个 元对象仅仅能和拥有一样内容的元对象相等。
因此这两个规则必须成立。
设计这两个函数所要注意到的:
假设你设计的对象类型并不使用于收集性对象。那么没有必要本身再设计这两个函数的处理方式。这是正确的面向对象设计方法,不论什么用户一时用不到的功能,就先不要设计,以避免给往后功能扩展带来麻烦。
假设你在设计时想别出心裁。不遵照以上的两套规则。那么劝你仍是不要作这样想入非非的事。我尚未遇到过哪个开发人员和我说设计这两个函数要违背前面说的两个规则,我碰到这些违反规定的状况时。都是做为设计错误处理。
当 一个对象类型做为收集型对象的元对象时。这个对象应该拥有本身处理equals(),和/或处理hashCode()的设计,而且要遵照前面所说 的两种原则。equals()先要查null和是不是同一类型。
查同一类型是为了不出现ClassCastException这样的异常给丢出来。查 null是为了不出现NullPointerException这样的异常给丢出来。
假设你的对象里面容纳的数据过多。那么这两个函数 equals()和hashCode()将会变得效率低。假设对象中拥有没法serialized的数据。equals()有可能在操做中出现错误。想象 一个对象x。它的一个整型数据是transient型(不能被serialize成二进制数据流)。然而equals()和hashCode()都有依靠 这个整型数据,那么,这个对象在serialization以前和以后。是否同样?答案是不同。因为serialization以前的整型数据是有效的 数据,在serialization以后,这个整型数据的值并无存储下来。再又一次由二进制数据流转换成对象后,二者(对象在serialization 以前和以后)的状态已经不一样了。
这也是要注意的。
============================================================
级别:入门级
每个Java对象都有hashCode()和 equals()方法。不少类忽略(Override)这些方法的缺省实施,以在对象实例之间提供更深层次的语义可比性。在Java理念和实践这一部分。 Java开发人员Brian Goetz向您介绍在建立Java类以有效和准肯定义hashCode()和equals()时应遵循的规则和指南。
您可以在讨论论坛与做者和其余读者一 同探讨您对本文的见解。
(您还可以点击本文顶部或底部的讨论进入论坛。)
尽管Java语言不直接支持关联数组 – 可以使用不论什么对象做为一个索引的数组 – 但在根Object类中使用hashCode()方法明白表示指望普遍使用HashMap(及其前辈Hashtable)。
理想状况下基于散列的容器提供 有效插入和有效检索;直接在对象模式中支持散列可以促进基于散列的容器的开发和使用。
定义对象的相等性
Object类有两种方法来判断对象的标识:equals()和hashCode()。通常来讲,假设您忽略了当中一种,您必须同一时候忽略这两种。因为二者 之间有必须维持的相当重要的关系。特殊状况是依据equals() 方法。假设两个对象是相等的,它们必须有一样的hashCode()值(尽管这一般不是真的)。
特定类的equals()的语义在Implementer的左側定义。定义对特定类来讲equals()意味着什么是其设计工做的一部分。Object提供的缺省实施简单引用如下等式:
public boolean equals(Object obj) { return (this == obj); }
在这样的缺省实施状况下,仅仅有它们引用真正同一个对象时这两个引用才是相等的。一样,Object提供的 hashCode()的缺省实施经过将对象的内存地址对映于一个整数值来生成。因为在某些架构上,地址空间大于int值的范围,两个不一样的对象有一样的 hashCode()是可能的。假设您忽略了hashCode()。您仍旧可以使用System.identityHashCode()方法来接入这类缺 省值。
忽略 equals() -- 简单实例
缺省状况下。equals()和hashCode()基于标识的实施是合理的。但对于某些类来讲,它们但愿放宽等式的定义。好比。Integer类定义equals() 与如下类似:
public boolean equals(Object obj) {
return (obj instanceof Integer
&& intValue() == ((Integer) obj).intValue());
}
在这个定义中,仅仅有在包括一样的整数值的状况下这两个Integer对象是相等的。结合将不可改动的 Integer。这使得使用Integer做为HashMap中的keyword是切实可行的。这样的基于值的Equal方法可以由Java类库中的所有原始封装类 使用。如Integer、Float、Character和Boolean以及String(假设两个String对象包括一样顺序的字符。那它们是相等 的)。
因为这些类都是不可改动的而且可以实施hashCode()和equals()。它们都可以作为很是好的散列keyword。
为何忽略 equals()和hashCode()?
假设Integer不忽略equals() 和 hashCode()状况又将怎样?假设咱们从未在HashMap或其余基于散列的集合中使用Integer做为keyword的话。什么也不会发生。但是,假设 咱们在HashMap中使用这类Integer对象做为keyword,咱们将不可以可靠地检索相关的值。除非咱们在get()调用中使用与put()调用中极其 类似的Integer实例。这要求确保在咱们的整个程序中。仅仅能使用相应于特定整数值的Integer对象的一个实例。不用说。这样的方法极不方便而且错误 频频。
Object的interface contract要求假设依据 equals()两个对象是相等的,那么它们必须有一样的hashCode()值。
当其识别能力整个包括在equals()中时,为何咱们的根对象类需 要hashCode()?hashCode()方法纯粹用于提升效率。Java平台设计人员估计到了典型Java应用程序中基于散列的集合类 (Collection Class)的重要性–如Hashtable、HashMap和HashSet。而且使用equals()与不少对象进行比較在计算方面很是昂贵。
使所 有Java对象都可以支持 hashCode()并结合使用基于散列的集合,可以实现有效的存储和检索。
==============================
为何HashCode对于对象是如此的重要?
一个对象的HashCode就是一个简单的Hash算法的实现,尽管它和那些真正的复杂的
Hash算法相比还不能叫真正的算法,但怎样实现它,不只仅是程序猿的编程水平问题,
而是关系到你的对象在存取时性能的很是重要的问题.有可能,不一样的HashCode可能
会使你的对象存取产生,成百上千倍的性能差异.
咱们先来看一下,在JAVA中两个重要的数据结构:HashMap和Hashtable,尽管它们有很是
大的差异,如继承关系不一样,对value的约束条件(是否赞成null)不一样,以及线程安全性
等有着特定的差异,但从实现原理上来讲,它们是一致的.因此,咱们仅仅以Hashtable来
说明:
在java中,存取数据的性能,通常来讲固然是首推数组,但是在数据量稍大的容器选择中,
Hashtable将有比数据性能更高的查询速度.详细缘由看如下的内容.
Hashtable在存储数据时,通常先将该对象的HashCode和0x7FFFFFFF作与操做,因为一个
对象的HashCode可以为负数,这样操做后可以保证它为一个正整数.而后以Hashtable的
长度取模,获得该对象在Hashtable中的索引.
index = (o.hashCode() & 0x7FFFFFFF)%hs.length;
这个对象就会直接放在Hashtable的第index位置,对于写入,这和数组同样,把一个对象
放在当中的第index位置,但假设是查询,通过一样的算法,Hashtable可以直接从第index
取得这个对象,而数组却要作循环比較.因此对于数据量稍大时,Hashtable的查询比数据
具备更高的性能.
既然可以依据HashCode直接定位对象在Hashtable中的位置,那么为何Hashtable
要用key来作映射呢(为了一些思惟有障碍的人能看到懂我加了一句话:而不是直接放value呢)
?
这就是关系Hashtable性能问题的最重要的问题:Hash冲突.
常见的Hash冲突是不一样对象终于产生了一样的索引,而一种很是甚至绝对少见的Hash冲突
是,假设一组对象的个数大过了int范围,而HashCode的长度仅仅能在int范围中,因此确定要
有同一组的元素有一样的HashCode,这样无论怎样他们都会有一样的索引.固然这样的极端
的状况是极少见的,可以暂不考虑,但对于一样的HashCode通过取模,则会产中一样的索引,
或者不一样的对象却具备一样的HashCode,固然具备一样的索引.
因此对于索引一样的对象,在该index位置存放了多个对象,这些值要想能正确区分,就要依
靠key自己和hashCode来识别.
事实上一个设计各好的HashTable,通常来讲会比較平均地分布每个元素,因为Hashtable
的长度老是比实际元素的个数按必定比例进行自增(装填因子通常为0.75)左右,这样大多
数的索引位置仅仅有一个对象,而很是少的位置会有几个对象.因此Hashtable中的每个位置存
放的是一个链表,对于仅仅有一个对象的位置,链表仅仅有一个首节点(Entry),Entry的next为
null.而后有hashCode,key,value. 属性保存了该位置的对象的HashCode,key和value(对象
自己),假设有一样索引的对象进来则会进入链表的下一个节点.假设同一个位置中有多个
对象,依据HashCode和key可以在该链表中找到一个和查询的key相匹配的对象.
从上面我看可以看到,对于HashMap和Hashtable的存取性能有重大影响的首先是应该使该
数据结构中的元素尽可能大可能具备不一样的HashCode,尽管这并不能保证不一样的HashCode
产生不一样的index,但一样的HashCode必定产生一样的index,从而影响产生Hash冲突.
对于一个象,假设具备很是多属性,把所有属性都參与散列,显然是一种笨拙的设计.因为对象
的HashCode()方法差点儿无所不在地被本身主动调用,如equals比較,假设太多的对象參与了散列.
那么需要的操做常数时间将会添加很是大.因此,挑选哪些属性參与散列绝对是一个编程水平
的问题.
从实现来讲,通常的HashCode方法会这样:
return Attribute1.HashCode() + Attribute2.HashCode()…[+super.HashCode()],
咱们知道,每次调用这种方法,都要又一次对方法内的參与散列的对象又一次计算一次它们的
HashCode的运算,假设一个对象的属性没有改变,仍然要每次都进行计算,因此假设设置一
个标记来缓存当前的散列码,仅仅要当參与散列的对象改变时才又一次计算,不然调用缓存的
hashCode,这可以从很是大程度上提升性能.
默认的实现是将对象内部地址转化为整数做为HashCode,这固然能保证每个对象具备不一样
的HasCode,因为不一样的对象内部地址确定不一样(废话),但java语言并不能让程序猿获取对
象内部地址,因此,让每个对象产生不一样的HashCode有着很是多可研究的技术.
怎样从多个属性中採样出能具备多样性的hashCode的属性,这是一个性能和多样性相矛
盾的地方,假设所有属性都參与散列,固然hashCode的多样性将大大提升,但牺牲了性能,
而假设仅仅有少许的属性採样散列,极端状况会产生大量的散列冲突,如对”人”的属性中,如
果用性别而不是姓名或出生日期,那将仅仅有两个或几个可选的hashcode值,将产生一半以上
的散列冲突.因此假设可能的条件下,专门产生一个序列用来生成HashCode将是一个好的选
择(固然产生序列的性能要比所有属性參与散列的性能高的状况下才行,不然还不如直接用
所有属性散列).
怎样对HashCode的性能和多样性求得一个平衡,可以參考相关算法设计的书,事实上并不必定 要求很是的优秀,仅仅要能尽最大可能减小散列值的汇集.重要的是咱们应该记得HashCode对 于咱们的程序性能有着生要的影响,在程序设计时应该时时加以注意.