hashcode的理解

============================================================ 
如何理解hashCode的做用:
============================================================ 
以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上的链表上。那么此时就只能根据Object的equal方法来比较这个对象是否equal。当两个对象的hashcode不一样的话,确定他们不能equal.

============================================================   
改写equals时老是要改写hashCode
============================================================
java.lnag.Object中对hashCode的约定:

   1. 在一个应用程序执行期间,若是一个对象的equals方法作比较所用到的信息没有被修改的话,则对该对象调用hashCode方法屡次,它必须始终如一地返回同一个整数。
   2. 若是两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
   3. 若是两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不一样的整数结果。但若是能不一样,则可能提升散列表的性能。

   
有一个概念要牢记,两个相等对象的equals方法必定为true, 但两个hashcode相等的对象不必定是相等的对象。

因此hashcode相等只能保证两个对象在一个HASH表里的同一条HASH链上,继而经过equals方法才能肯定是否是同一对象,若是结果为true, 则认为是同一对象在插入,不然认为是不一样对象继续插入。

Object的代码:java

public String toString () {
    return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
}

public boolean equals (Object o) {
    return this == o;
}

/**
 * Answers an integer hash code for the receiver. Any two
 * objects which answer <code>true</code> when passed to
 * <code>.equals</code> must answer the same value for this
 * method.
 *
 * @author        OTI
 * @version        initial
 *
 * @return        int
 *                    the receiver's hash.
 *
 * @see            #equals
 */
public native int hashCode();

从上面咱们能够看到是否极可能Object.hashCode就是表明内存地址。下面咱们来证实hashcode是否是真的就是Object的内存地址呢?实际上,hashcode根本不能表明object的内存地址。
-----------------------------------------
Object.hashCode不能够表明内存地址
----------------------------------------程序员

package com.tools;

import java.util.ArrayList;

/**
 * 此方法的做用是证实 java.lang.Object的hashcode 不是表明 对象所在内存地址。
 * 我产生了10000个对象,这10000个对象在内存中是不一样的地址,可是实际上这10000个对象
 * 的hashcode的是彻底可能相同的
 */
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());
    }
}

==============================
看HashTable的源代码很是有用:
==============================   

==============================   
HashCode方法使用简介      
==============================   
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 
%26amp;%26amp; intValue() == ((Integer) obj).intValue()); 

在这个定义中,只有在包含相同的整数值的状况下这两个Integer对象是相等的。结合将不可修改的Integer,这使得使用Integer做为HashMap中的关键字是切实可行的。这种基于值的Equal方法能够由Java类库中的全部原始封装类使用,如Integer、Float、Character和Boolean以及String(若是两个String对象包含相同顺序的字符,那它们是相等的)。因为这些类都是不可修改的而且能够实施hashCode()和equals(),它们均可以作为很好的散列关键字。 
为何忽略 equals()和hashCode()? 
若是Integer不忽略equals() 和 hashCode()状况又将如何?若是咱们从未在HashMap或其它基于散列的集合中使用Integer做为关键字的话,什么也不会发生。可是,若是咱们在HashMap中使用这类Integer对象做为关键字,咱们将不可以可靠地检索相关的值,除非咱们在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() 必须一致 -- 它们的结果在随后的调用中将是相同的,假设“不改变对象相等性比较中使用的任何信息。”这听起来象“计算的结果将不改变,除非实际状况如此。”这一模糊声明一般解释为相等性和散列值计算应是对象的可肯定性功能,而不是其它。 
对象相等性意味着什么? 
人们很容易知足Object类规范对equals() 和 hashCode() 的要求。决定是否和如何忽略equals()除了判断之外,还要求其它。在简单的不可修值类中,如Integer(事实上是几乎全部不可修改的类),选择至关明显 -- 相等性应基于基本对象状态的相等性。在Integer状况下,对象的惟一状态是基本的整数值。 
对于可修改对象来讲,答案并不老是如此清楚。equals() 和hashCode() 是否应基于对象的标识(象缺省实施)或对象的状态(象Integer和String)?没有简单的答案 -- 它取决于类的计划使用。对于象List和Map这样的容器来讲,人们对此争论不已。Java类库中的大多数类,包括容器类,错误出如今根据对象状态来提供equals()和hashCode()实施。 
若是对象的hashCode()值能够基于其状态进行更改,那么当使用这类对象做为基于散列的集合中的关键字时咱们必须注意,确保当它们用于做为散列关键字时,咱们并不容许更改它们的状态。全部基于散列的集合假设,当对象的散列值用于做为集合中的关键字时它不会改变。若是当关键字在集合中时它的散列代码被更改,那么将产生一些不可预测和容易混淆的结果。实践过程当中这一般不是问题 -- 咱们并不常用象List这样的可修改对象作为HashMap中的关键字。 
一个简单的可修改类的例子是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; 
  
A otherA = (A) other; 
return 
(someNonNullField.equals(otherA.someNonNullField)) 
%26amp;%26amp; ((someOtherField == null) 
? otherA.someOtherField == null 
: someOtherField.equals(otherA.someOtherField))); 

如今咱们定义了equals(),咱们必须以统一的方法来定义hashCode()。一种统一但并不老是有效的定义hashCode()的方法以下: 
public int hashCode() { return 0; } 
这种方法将生成大量的条目并显著下降HashMaps的性能,但它符合规范。一个更合理的hashCode()实施应该是这样: 
public int hashCode() { 
int hash = 1; 
hash = hash * 31 + someNonNullField.hashCode(); 
hash = hash * 31 
+ (someOtherField == null ? 0 : someOtherField.hashCode()); 
return hash; 

注意:这两种实施都下降了类状态字段的equals()或hashCode()方法必定比例的计算能力。根据您使用的类,您可能但愿下降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使用的诀窍都很简单,但算术上还远远不够完美。类库不提供任何散列算法的方便实施,它能够简化更先进的hashCode()实施的建立。 
当扩展已经忽略了equals()的 instantiable类时很难编写equals()。当扩展已经忽略了equals()的 instantiable类时,定义equals()的“显而易见的”方式都不能知足equals()方法的对称或传递性需求。这意味着当忽略equals()时,您必须了解您正在扩展的类的结构和实施详细信息,甚至须要暴露基本类中的机密字段,它违反了面向对象的设计的原则。 
结束语 
经过统必定义equals()和hashCode(),您能够提高类做为基于散列的集合中的关键字的使用性。有两种方法来定义对象的相等性和散列值:基于标识,它是Object提供的缺省方法;基于状态,它要求忽略equals()和hashCode()。当对象的状态更改时若是对象的散列值发生变化,确信当状态做为散列关键字使用时您不容许更更改其状态。 




解析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()有可能相等,也有可能不等。 

为何这两个规则是这样的,缘由其实很简单,拿HashSet来讲吧,HashSet能够拥有一个或更多的箱子,在同一个箱子中能够有一个 或更多的独特元对象(HashSet所容纳的必须是独特的元对象)。这个例子说明一个元对象能够和其余不一样的元对象拥有相同的hashCode。可是一个 元对象只能和拥有一样内容的元对象相等。因此这两个规则必须成立。

设计这两个函数所要注意到的:
若是你设计的对象类型并不使用于收集性对象,那么没有必要本身再设计这两个函数的处理方式。这是正确的面向对象设计方法,任何用户一时用不到的功能,就先不要设计,以避免给往后功能扩展带来麻烦。

若是你在设计时想别出心裁,不遵照以上的两套规则,那么劝你仍是不要作这样想入非非的事。我尚未遇到过哪个开发者和我说设计这两个函数要违背前面说的两个规则,我碰到这些违反规则的状况时,都是做为设计错误处理。

当一个对象类型做为收集型对象的元对象时,这个对象应该拥有本身处理equals(),和/或处理hashCode()的设计,并且要遵照前面所说 的两种原则。equals()先要查null和是不是同一类型。查同一类型是为了不出现ClassCastException这样的异常给丢出来。查 null是为了不出现NullPointerException这样的异常给丢出来。

若是你的对象里面容纳的数据过多,那么这两个函数 equals()和hashCode()将会变得效率低。若是对象中拥有没法serialized的数据,equals()有可能在操做中出现错误。想象 一个对象x,它的一个整型数据是transient型(不能被serialize成二进制数据流)。然而equals()和hashCode()都有依靠 这个整型数据,那么,这个对象在serialization以前和以后,是否同样?答案是不同。由于serialization以前的整型数据是有效的 数据,在serialization以后,这个整型数据的值并无存储下来,再从新由二进制数据流转换成对象后,二者(对象在serialization 以前和以后)的状态已经不一样了。这也是要注意的。 


============================================================      
有效和正肯定义hashCode()和equals():
============================================================      

  级别:入门级


  每一个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中的关键字是切实可行的。这种基于值的Equal方法能够由Java类库中的全部原始封装类使用,如Integer、Float、Character和Boolean以及String(若是两个String对象包含相同顺序的字符,那它们是相等的)。因为这些类都是不可修改的而且能够实施hashCode()和equals(),它们均可以作为很好的散列关键字。

  为何忽略 equals()和hashCode()?

  若是Integer不忽略equals() 和 hashCode()状况又将如何?若是咱们从未在HashMap或其它基于散列的集合中使用Integer做为关键字的话,什么也不会发生。可是,若是咱们在HashMap中使用这类Integer对象做为关键字,咱们将不可以可靠地检索相关的值,除非咱们在get()调用中使用与put()调用中极其相似的Integer实例。这要求确保在咱们的整个程序中,只能使用对应于特定整数值的Integer对象的一个实例。不用说,这种方法极不方便并且错误频频。

  Object的interface contract要求若是根据 equals()两个对象是相等的,那么它们必须有相同的hashCode()值。当其识别能力整个包含在equals()中时,为何咱们的根对象类须要hashCode()?hashCode()方法纯粹用于提升效率。Java平台设计人员预计到了典型Java应用程序中基于散列的集合类(Collection Class)的重要性--如Hashtable、HashMap和HashSet,而且使用equals()与许多对象进行比较在计算方面很是昂贵。使全部Java对象都可以支持 hashCode()并结合使用基于散列的集合,能够实现有效的存储和检索。算法

==============================
Go deep into 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对于咱们的程序性能有着生要的影响,在程序设计时应该时时加以注意.编程

相关文章
相关标签/搜索