Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 429|回复: 0

开发交流:hashcode详解

[复制链接]

该用户从未签到

发表于 2011-10-24 09:51:31 | 显示全部楼层 |阅读模式
1.hashCode()方法使用来提高Map里面的搜索效率的,Map会根据不同的hashCode()来放在不同的桶里面,Map在搜索一个对象的时候先通过hashCode()找到相应的桶,然后再根据equals()方法找到相应的对象.要正确的实现Map里面查找元素必须满足一下两个条件:
(1)当obj1.equals(obj2)为true时obj1.hashCode()   ==   obj2.hashCode()必须为true
(2)当obj1.hashCode()   ==   obj2.hashCode()为false时obj.equals(obj2)必须为false

hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable   提供的哈希表。   

hashCode   的常规协定是:   
在   Java   应用程序执行期间,在同一对象上多次调用   hashCode   方法时,必须一致地返回相同的整数,前提是对象上   equals   比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。   
如果根据   equals(Object)   方法,两个对象是相等的,那么在两个对象中的每个对象上调用   hashCode   方法都必须生成相同的整数结果。   
以下情况不   是必需的:如果根据   equals(java.lang.Object)   方法,两个对象不相等,那么在两个对象中的任一对象上调用   hashCode   方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。   
实际上,由   Object   类定义的   hashCode   方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是   JavaTM   编程语言不需要这种实现技巧。)   

当equals方法被重写时,通常有必要重写   hashCode   方法,以维护   hashCode   方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
例如内存中有这样的位置
0     1     2     3     4     5     6     7     
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
但如果用hashcode那就会使效率提高很多。
我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除8求余数直接找到存放的位置了。

但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义   equals了。
也就是说,我们先通过   hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过   equals   来在这个桶里找到我们要的类。
那么。重写了equals(),为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊
你要对A类排序,有两种方法,一种就是让A类实现comparabole结构并实现compareTo()方法,那么可以通过Collections.sort(List <A>   list)对其进行排序
另一种方法:自己定义一个类B实现Comparator类并实现compare方法,
然后通过Collections.sort(List <A>   list,B   b)进行排序
hashCode()是用来产生哈希玛的,而哈希玛是用来在散列存储结构中确定对象的存储地址的,(这一段在   Java编程思想   中讲的很清楚的)象util包中的   带   hash   的集合类都是用这种存储结构   :HashMap,HashSet,   他们在将对象存储时(严格说是对象引用),需要确定他们的地址吧,   而HashCode()就是这个用途的,一般都需要重新定义它的,因为默认情况下,由   Object   类定义的   hashCode   方法会针对不同的对象返回不同的整数,这一般是通过将该对象的内部地址转换成一个整数来实现的,现在举个例子来说,   就拿HashSet来说   ,在将对象存入其中时,通过被存入对象的   hashCode()   来确定对象在   HashSet   中的存储地址,通过equals()来确定存入的对象是否重复,hashCode()   ,equals()都需要自己重新定义,因为hashCode()默认前面已经说啦,而equals()   默认是比较的对象引用,你现在想一下,如果你不定义equals()的话,那么同一个类产生的两个内容完全相同的对象都可以存入Set,因为他们是通过equals()来确定的,这样就使得HashSet   失去了他的意义,看一下下面这个:
import   java.util.*;
class   A   
{
private   int   i;
public   A(int   i){
this.i   =   i;
}
public   String   toString(){
return   "   "+i;
}
public   boolean   equals(Object   o){
A   a   =   (A)o;
return   (a.i==i)   ?   true   :   false   ;
}
public   int   hashCode(){
return   i;
}


}

public   class   TestA
{
public   static   void   main(String[]   args){
HashSet   set   =   new   HashSet();
for(int   i   =   0;i <=3;i++)
set.add(new   A(i));
System.out.println(set);
set.add(new   A(1));
System.out.println(set);
System.out.println(set.contains(new   A(0)));
System.out.println(set.add(new   A(1)));
System.out.println(set.add(new   A(4)));
System.out.println(set);


}

}
你分别注释掉hashCode()和   equals()来比较一下他们作用就可以拉,关键要自己动手看看比较的结果你就可以记得很清楚啦

comparable接口作用是使得对象天生就具有比较的功能,它是为了支持Arrays.sort(Object[],   Comparator),TreeSet,TreeMap   这样集合类而存在的,这些就需要你自己去查文档拉   注意的是TreeSet,TreeMap   确定存入的对象是否重复时   用的不是equals()   而是comparable的   compareTo(T   o)   方法   。
Comparator接口是一个比较器,他的作用与comparable   的作用差不多,不过他是通过参数传递来实现的   象   :   TreeSet   的构造器中不是有这么一个方法吗:   public   TreeSet(Comparator <?   super   E>   c),你自己去查查看,确定存入的对象是否重复时用的就是   int   compare(T   o1,T   o2);
系统什么时侯会回调comparare方法呢?你去看看   Arrays   中的sort   方法就知道啦   !!!!

以下内容参见了如下网址:
1、http://blog.csdn.net/ngqzmjmj/archive/2005/04/27/365149.aspx
2、http://blog.csdn.net/pbnow/archive/2006/04/25/677253.aspx
3、http://blog.csdn.net/petercheng456/archive/2005/08/02/444427.aspx
讲到Hashtable和HashMap的区别的时候,就不得不说说hashCode的问题,下面是自己从网上找到的几篇资料,自己结合HashTable来整理了一下:
1、任何class如果覆写了equals()方法,就必须覆写hashCode()。
这样作的目的就是为了你的类就能够很好的与java的集合框架协同工作。如果我们能够确认我们定义的类不会和java集合类产生关系,那么我们完全没有必要在覆写equals()方法的时候覆写hashCode。
2、关于Hashtable,判断key是否相同的条件是:hashCode()相同 && 满足equals()。这两个方法都是可以重构的,所以Hashtable没有用==,给了程序员实现自己的Hashtable的好的途径,的确Sun想得很严密。
例子1:
      class a{
            private int b;  
            public a(int c){//构造函数
             b=c;
          }
      public String eqals(Object o){
       if(this==o) return true;
        if(o instanceof a){
         return a.b==(a)o.b;
        }
        return false;
              }
      public static void main(String[] args){
       Map p=new HashMap();
       p.put(new a(1));   
       p.get(new a(1))
       /*这里返回为null,因为map是根据equals()和hashCode()来判断对象是否相等,所以在类里覆写了equals(),就一定要覆写hashCode()。关于Hashtable,判断key是否相同的条件是:hashCode()相同 && 满足equals(),而一个类如果没有覆写equals()方法,那么这个类的equals方法比较的是对象的内存地址。如果没有覆写HashCode,那么该类的hashCode是通过对象的内存地址转换而来.
       */
         }
       }
       例子二:
       /**
* 类:TestStudent
*/
        public class TestStudent {
         private int age;
         public TestStudent(int age) {
             this.age = age;
         }
         public TestStudent() {
         }
         public boolean equals(Object o) {
//            if (this == o) return true;
//            if (o == null || getClass() != o.getClass()) return false;
//
//            final TestStudent that = (TestStudent) o;
//
//            if (age != that.age) return false;
             return true;
         }
         public int hashCode() {
             return age;
         }
}
/**
* 类:TestTeacher
*/
public class TestTeacher {
         private int age;
         public TestTeacher(int age) {
             this.age = age;
         }
         public TestTeacher() {
         }
         public boolean equals(Object o) {
//            if (this == o) return true;
//            if (o == null || getClass() != o.getClass()) return false;
//
//            final TestTeacher that = (TestTeacher) o;
//
//            if (age != that.age) return false;
//
//            return true;
             return true;
         }
         public int hashCode() {
             return age;
         }
}
public class TestHashtable2 {
         public static void main(String[] args){
             Hashtable table=new Hashtable();
             table.put(new TestTeacher(1),"aaa");
             System.out.print("=========="+table.get(new TestStudent(1)));
         }
}
输出结果是:==========aaa
从上面这个例子就可以看出来Hashtable中key和对象所属的class无关,只和equals方法和hashCode方法的有关。当然正常情况下我们覆写equals方法的时候下面两句话是必不可少的。
if (this == o) return true;//保证了统一对对象的绝对一致性。
if (o == null || getClass() != o.getClass()) return false;
final TestStudent that = (TestStudent) o;//保证了只有同一个类的不同对象进行equals,虽然上面的例子可以进行equals对比不同的对象,但是实际情况中不同对象间的equals进行对比意义不大。
if (age != that.age){
     return false;
}
3、在程序执行期间,同一个对象调用hashCode()必须返回同一个值(同一个应用执行期)
4、如果两个对象equals,那么他们的hashCode()必须相等。(个人感觉这个要求也是为了和java的结合框架协同工作,因为我们自己定义的类完全可以覆写equals方法,但是不覆写hashCode,尽管我们可以这样做,但是这样做是不合理的,他会为我们的程序埋下错误隐患。虽然java没有从在编译器对上面的说法进行控制,但是上面的说法是我们在设计类的时候应该遵循的规则)。
5、如果两个对象equals不相等,那么他们的hashCode()不必产生不同的结果(上面的例2就可以说明问题),程序员应该注意到,对不同的对象产生不同的hashCode(),有可能提升 hash table(哈希表)的效率
7、Map.put(key,value)时根据key.hashCode生成一个内部hash值,根据这个hash值将对象存放在一个table中。Map.get(key)会比较key.hashCode和equals方法,当且仅当这两者相等时,才能正确定位到table;java中Set是通过Map实现的,所以Map和Set的所有实现类都要注意这一点.
8、
StringBuffer buffer = new StringBuffer();
buffer.append("some");
String a = buffer.toString();
String b = "some";
现在有三个式子,结果在后面用注释的形式标出:
a.equals(b);//true
a==b;//false
a.hashCode()==b.hashCode();//true
说明:"ab"=="ab"是因为编译的时候就把这两个指向同一个常量了,属于编译优化的一部分。但是动态生成的字符串的地址就不一样了。比如String a = buffer.toString(), 但是buffer.append("some")和String b = "some"里两个"some"常量应当是指向同一个地址。
9、如果想判断是否两个对象引用指向同一个实体,唯一的正确途径是使用==,因为只有它不会被重构,要慎用hashCode
10、有一点我们应该是能够确认的,就是同样的数字转换成hashCode后的值肯定一样,因此我们在覆写hashCode时候应该利用自己编写的类独特属性进行hashCode运算(当然不仅仅是一个属性,可以是多个属性),这样我们就可以尽量的保证每个类对象的具有不同的hashCode,如果这些不同的对象有机会进入同一个map的时候就可以保证其hashCode不一样,从而提高map的检索。(下面文章中提到了,在一个map中的对象应该尽量的避免其hashCode一样)。
11、关于hashCode的常规写法:把所有属性都参与散列,通常的算法就是将不同的质数与字段做乘积和,比如一个类具有两个属性age和name,那么return    11*age+13*name;。至于为什么用质数的问题个人就没有深入研究了。对于我们对算法要求不是很高的我们当然可以让所有属性都参与散列,但是如果我们为了追求一点效率,那么我觉的应该只将类的独特字段进行散列就可以了。
12、另外一点就是充分利用IDE,刚开始看这个问题的时候,只知道覆写了equals就应该覆写hashCode,但是hashCode该怎么写,自己也不是很清楚,通过自己的IDE,Intellij IDEA自动生成的hashCode,就了解到了将类的所有属性都参与散列的方法,个人觉的,IDE给出的算法虽然不是针对某个类最优的算法,但是对于我们一般的应用程序,对算法要求不高的程序,还是很有参考价值的。
以下内容是转帖http://dev2dev.bea.com.cn/bbsdoc/20060807307.html
为什么HashCode对于对象是如此的重要?
  一个对象的HashCode就是一个简单的Hash算法的实现,虽然它和那些真正的复杂的Hash算法相比还不能叫真正的算法,它如何实现它,不仅仅是程序员的编程水平问题,而是关系到你的对象在存取是性能的非常重要的关系.有可能,不同的HashCode可能会使你的对象存取产生,成百上千倍的性能差别。
  我们先来看一下,在JAVA中两个重要的数据结构:HashMap和Hashtable,虽然它们有很大的区别,如继承关系不同,对value的约束条件(是否允许null)不同,以及线程安全性等有着特定的区别,但从实现原理上来说,它们是一致的.所以,我们只以Hashtable来说明:
  在java中,存取数据的性能,一般来说当然是首推数组,但是在数据量稍大的容器选择中,Hashtable将有比数组性能更高的查询速度.具体原因看下面的内容。
  Hashtable在存储数据时,一般先将作为key的对象的HashCode和0x7FFFFFFF做与操作,因为一个对象的HashCode可以为负数,这样操作后可以保证它为一个正整数.然后以Hashtable的长度取模,得到值对象在Hashtable中的索引。
  index = (o.hashCode() & 0x7FFFFFFF)%hs.length;这个值对象就会直接放在Hashtable的第index位置,对于写入,这和数组一样,把一个对象放在其中的第index位置,但如果是查询,经过同样的算法,Hashtable可以直接通过key得到index,从第index取得这个值对象,而数组却要做循环比较.所以对于数据量稍大时,Hashtable的查询比数据具有更高的性能。
  虽然不同对象有不同的hashcode,但不同的hashCode经过与长度的取余,就很可能产生相同的index。
  极端情况下会有大量的对象产生一个相同的索引.这就是关系Hashtable性能问题的最重要的问题:
  Hash冲突。
  常见的Hash冲突是不同key对象最终产生了相同的索引,而一种非常甚至绝对少见的Hash冲突是,如果一组对象的个数大过了int范围,而HashCode的长度只能在int范围中,所以肯定要有同一组的元素有相同的HashCode,这样无论如何他们都会有相同的索引.当然这种极端的情况是极少见的,可以暂不考虑,但是对于同的HashCode经过取模,则会产中相同的索引,或者不同的对象却具有相同的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() + Attribute1.HashCode()..[+super.HashCode()]。
  我们知道,每次调用这个方法,都要重新对方法内的参与散列的对象重新计算一次它们的HashCode的运算,如果一个对象的属性没有改变,仍然要每次都进行计算,所以如果设置一个标记来缓存当前的散列码,只要当参与散列的对象改变时才重新计算,否则调用缓存的hashCode,这可以从很大程度上提高性能。
  默认的实现是将对象内部地址转化为整数作为HashCode,这当然能保证每个对象具有不同的HasCode,因为不同的对象内部地址肯定不同(废话),但java语言并不能让程序员获取对象内部地址,所以,让每个对象产生不同的HashCode有着很多可研究的技术。
  如果从多个属性中采样出能具有平均分布的hashCode的属性,这是一个性能和多样性相矛盾的地方,如果所有属性都参与散列,当然hashCode的多样性将大大提高,但牺牲了性能,而如果只能少量的属性采样散列,极端情况会产生大量的散列冲突,如对"人"的属性中,如果用性别而不是姓名或出生日期,那将只有两个或几个可选的hashcode值,将产生一半以上的散列冲突.所以如果可能的条件下,专门产生一个序列用来生成HashCode将是一个好的选择(当然产生序列的性能要比所有属性参与散列的性能高的情况下才行,否则还不如直接用所有属性散列)。
  如何对HashCode的性能和多样性求得一个平衡,可以参考相关算法设计的书,其实并不一定要求非常的优秀,只要能尽最大可能减少散列值的聚集.重要的是我们应该记得HashCode对于我们的程序性能有着生要的影响,在程序设计时应该时时加以注意。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

GMT+8, 2025-1-10 22:52 , Processed in 0.329363 second(s), 36 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表