热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

小弟初学困扰多日hashcode问题!

大家先看完有关hashcode的介绍再看看我的例子程序,麻烦大家了。这问题困扰小弟多天了。有许多人学了很长时间的Java,但一直不明白hashCode方法的作用,我来解释一下吧。首
大家先看完有关hashcode的介绍再看看我的例子程序,麻烦大家了。这问题困扰小弟多天了。

有许多人学了很长时间的Java,但一直不明白hashCode方法的作用,我来解释一下吧。
    首先,想要明白hashCode的作用,你必须要先知道Java中的集合。

    总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是 Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。

    于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。

    这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

    所以,Java对于eqauls方法和hashCode方法是这样规定的:
      1、如果两个对象相同,那么它们的hashCode值一定要相同;
      2、如果两个对象的hashCode相同,它们并不一定相同

    上面说的对象相同指的是用eqauls方法比较。
    你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降。

程序:
import java.util.*;

public class TestHashCode1{
    public static void main(String[] args) {
Name n1=new Name("f1","n1");
Name n2=new Name("f1","n1");
String s1=new String("w");
String s2=new String("w");
System.out.println(n1.hashCode());
System.out.println(n2.hashCode());//结果n1和n2的hashcode并不相同
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());//s1和s2的hashcode相同
System.out.println(n1.equals(n2));//true
Collection c = new HashSet();
        c.add("hello");
        c.add(new Name("f1","l1"));
        c.add(new Integer(100));
        c.remove("hello"); 
        c.remove(new Integer(100));
        System.out.println
                  (c.remove(new Name("f1","l1")));//false
        System.out.println(c);

    }


}

class Name  {
    private String firstName,lastName;
    public Name(String firstName, String lastName) {
        this.firstName = firstName; this.lastName = lastName;
    }
  
    public String toString() {  return firstName + " " + lastName;  }
    
    public boolean equals(Object obj) {
    if (obj instanceof Name) {
        Name name = (Name) obj;
        return (firstName.equals(name.firstName))
            && (lastName.equals(name.lastName));
    }
    return super.equals(obj);
}
//public int hashCode() {
//   return firstName.hashCode();
//}

}


这程序中,开始我并没有重写Name的hashCode方法,结果如注释所示,大家可以看到n1和n2这两个对象相同(equals)但它们的hashcode不同。所以并没有删除New Name("f1","l1")。
这里就跟上面说的矛盾了
 1、如果两个对象相同,那么它们的hashCode值一定要相同;

当我重写hashCode方法后,返回firstName.hashCode()强行使n1和n2的hashcode的返回值相同后,就成功删除了
New Name("f1","l1")这种方法使我不能接受!
发现一个问题,如果不重写Name的toString方法,直接打印n1的话@后面的就是n1的hashcode值的十六进制!
麻烦高手解答小弟疑惑!
不慎感激!

18 个解决方案

#1


你自己查得很明确了。
两个不同的对象,hashcode不同。
String a = new String("abc");
String b = new String("abc");
两个不同的对象,但是值相同。

hashcode用于判断对象的地址是否相同,而不是值。

#2


Name n1=new Name("f1","n1"); 
Name n2=new Name("f1","n1"); 
n1和n2不是两个相同的对象,只是他们的值相同。地址是不同的,所以equal相同,hashcode是不同的。

#3


可能我没说清楚我的问题,那s1和s2也是不同的对象为什么它们的hashcode的值相同呢?

#4


equals()相等的两个对象,hashcode()一定相等; 
反过来:hashcode()不等,一定能推出equals()也不等; 
hashcode()相等,equals()可能相等,也可能不等。 

#5


如果你打印一个对象,不重些toString()方法,它就会调用默认你的toString()打印这个些的类名加这个这个类名的十六十六进制。

#6


理解存在偏差。
“要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是 Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。”
Java实现者怎么可能笨到这种地步。内部怎么实现那可是一门深奥的学问呢,设计很多高级的数据结构。否则还要用它提供的Collection干什么!自己也写一个呗。

“于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。”
这个理解确实荒唐,按这种说法Hash在不同的电脑上对同一个字符串难道还会返回不同的值,移植性从何谈起。

#7


赞,楼主真细心!

我举例子的时候,因为偷懒,不恰当的使用了String类。

String这个类在java中是一个很特殊的类。因为在一般情况下,大部分代码都在做字符串的操作。而同时保存很多字符串又需要消耗大量内存。实际中,我们的程序中会出现很多重复的字符串资源。

针对这样的情况,java的虚拟机实现中,对String这个类做了特殊处理。在内存中又一个部分被称作“字符串”池。程序中的字符串被保存在这个池中,值相同的字符串在池中只保留一份。这样大量的减少了字符串存储的所需空间。

也就是说:
String s1 = new String("a ");
String s2 = new String("a ");
这样的代码,实际上在内存中,只有在字符串池中,保存一个"a "这样的字符串。s1和s2是这个字符串的两个引用。

因为同一个字符串被多个引用,如果一个引用修改这个值,会导致所有引用的值都改变。这样就使错误的。为了避免这样的问题出现。java的实现中,字符串池中的字符串具有不变性。也就是说,一点字符串创立,那么他在jvm中永远不会改变。
比如:
String s1 = new String("a ");//a空格
String s2 = new String("a ");

s1=s1.trim();
这个操作实际上是生成了一个新的字符串保存s1.trim()方法的结果"a",然后将s1的引用更新为这个新字符串。
这是这个特性,也导致类s1+s2的时候,实际上是产生了一个新的字符串,保存着s1+s2的值。也就是为什么做java的程序员会被告诫用StringBuffer做字符串连接。

后头我们看你的问题。
String s1 = new String("a ");
String s2 = new String("a ");
中,s1和s2的值相同,所以equalse方法为true,因为s1和s2都是通过new获得的,s1与s2是两个类似c中指针的空间,这两个空间由new分配。所以s1与s2并不相等(==为false).
最后我们说hashCode。这两个字符串的值,实际上在同一空间,即字符串池中的"a ",hashCode代表内存地址的位置,因为在寻找这个值得时候,可以用hashCode定位,做快速的内存操作,所以如果hashCode是s1和s2两个指针的地址,那么根据他定位出来的是两个指针,不能做值得比较。而HashCode实际上保存的时最终的那个池中的"a "的地址,所以s1和s2的hashCode相同。

总结下:
s1.equals(s2);//true
s1==s2;//false
因为字符串池的原因s1.hashCode()==s2.hashCode()。

有不完整或有疑义的地方,各位补充、探讨。
感谢楼主!

#8


感谢楼上的耐心解答!这问题越来越有意思了!希望更多的朋友进来讨论!
大家来看看n1和n2 它们equals为true但它们的hashcode不相等,然后c.remove(new Name("f1","l1"))时,它与n1是equals的,但remove方法会先比较它们的hashcode值,显然它们的值并不相等,所以返回fluse。
当我重写hashCode方法后,返回firstName.hashCode()强行使n1和n2的hashcode的返回值相同后,c.remove(new Name("f1","l1"))返回true了。

#9


这个东西关键是弄懂内存,java中定义的类,当你new 对象的时候系统自动为其分配内存,上面的n1和n2 new了两次,所以占用两块内存,其hashcode必然不同;
java中的String类比较特殊,他和普通类的存储方式不同,他存储时自动进行优化,上面的s1和s2由于内容相同,所以占用一块内存,故其hashcode相同.

#10


看来很多人都有这个疑惑,s1和s2并不占用一块内存吧,它们都是new出来的,不可能占用同一块内存
     String s1=new String("w");
String s2=new String("w");
String s3="ww";
String s4="ww";
                System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s1==s2);//false
System.out.println(s4==s3);//ture

s1和s2并不==,但它们的hashcode相等,字符串的hashcode是这样计算的:
String 对象的哈希码按下列公式计算: 
 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
 使用 int 算法,这里 s[i] 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希码为 0。) 
只是根据字符串的内容而已!也就是只要字符串一致,hashcode的就相等。与是否==无关。请大家仔细研究下这个问题!
待解决。。。


#11


hashCode()是Object类的方法。String类重写了这个方法。所以一般意义上的HashCode的作用与String的不同。这些在API doc中说明了。代码如下,大家注意看注释:)

hashCode()方法在Object类中是Native方法,代码如下:

    /**
     * Returns a hash code value for the object. This method is 
     * supported for the benefit of hashtables such as those provided by 
     * java.util.Hashtable
     * 


     * The general contract of hashCode is: 
     * 


         * 
  • Whenever it is invoked on the same object more than once during 
         *     an execution of a Java application, the hashCode method 
         *     must consistently return the same integer, provided no information 
         *     used in equals comparisons on the object is modified.
         *     This integer need not remain consistent from one execution of an
         *     application to another execution of the same application. 
         * 
  • If two objects are equal according to the equals(Object)
         *     method, then calling the hashCode method on each of 
         *     the two objects must produce the same integer result. 
         * 
  • It is not required that if two objects are unequal 
         *     according to the {@link java.lang.Object#equals(java.lang.Object)} 
         *     method, then calling the hashCode method on each of the 
         *     two objects must produce distinct integer results.  However, the 
         *     programmer should be aware that producing distinct integer results 
         *     for unequal objects may improve the performance of hashtables.
         * 

     * 


     * As much as is reasonably practical, the hashCode method defined by 
     * class Object does return distinct integers for distinct 
     * objects. (This is typically implemented by converting the internal 
     * address of the object into an integer, but this implementation 
     * technique is not required by the 
     * JavaTM programming language.)
     *
     * @return  a hash code value for this object.
     * @see     java.lang.Object#equals(java.lang.Object)
     * @see     java.util.Hashtable
     */
    public native int hashCode();

    

String类中的hashCode()的实现:


    /**
     * Returns a hash code for this string. The hash code for a
     * String object is computed as
     * 

     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * 

     * using int arithmetic, where s[i] is the
     * ith character of the string, n is the length of
     * the string, and ^ indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    public int hashCode() {
int h = hash;
if (h == 0) {
    int off = offset;
    char val[] = value;
    int len = count;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
    }

#12


如果想看对象原始的,也就是 Object 所实现的 hashCode 的话,可以使用:
System.identityHashCode 这个方法,无论该类是否重写过 hashCode 方法都能调用。

#13


学习啦。。谢谢啦

#14


支持7楼的回答,String是一个引用类型。
在创建对象的时候,是创建的一个引用。
而s1,s2他们的值都是一样的。所以2个对象引用的同一个文中说到的物理路径。因而他们的hashCode是相同。

#15


引用楼主 gnsywenyuan 的回复:
大家先看完有关hashcode的介绍再看看我的例子程序,麻烦大家了。这问题困扰小弟多天了。

有许多人学了很长时间的Java,但一直不明白hashCode方法的作用,我来解释一下吧。
    首先,想要明白hashCode的作用,你必须要先知道Java中的集合。

    总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是 Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。

    于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。

    这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

    所以,Java对于eqauls方法和hashCode方法是这样规定的:
      1、如果两个对象相同,那么它们的hashCode值一定要相同;
      2、如果两个对象的hashCode相同,它们并不一定相同

    上面说的对象相同指的是用eqauls方法比较。
    你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降。

程序:
import java.util.*;

public class TestHashCode1{
    public static void main(String[] args) {
Name n1=new Name("f1","n1");
Name n2=new Name("f1","n1");
String s1=new String("w");
String s2=new String("w");
System.out.println(n1.hashCode());
System.out.println(n2.hashCode());//结果n1和n2的hashcode并不相同
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());//s1和s2的hashcode相同
System.out.println(n1.equals(n2));//true
Collection c = new HashSet();
        c.add("hello");
        c.add(new Name("f1","l1"));
        c.add(new Integer(100));
        c.remove("hello");
        c.remove(new Integer(100));
        System.out.println
                  (c.remove(new Name("f1","l1")));//false
        System.out.println(c);

    }


}

class Name  {
    private String firstName,lastName;
    public Name(String firstName, String lastName) {
        this.firstName = firstName; this.lastName = lastName;
    }
 
    public String toString() {  return firstName + " " + lastName;  }
   
    public boolean equals(Object obj) {
    if (obj instanceof Name) {
        Name name = (Name) obj;
        return (firstName.equals(name.firstName))
            && (lastName.equals(name.lastName));
    }
    return super.equals(obj);
}
//public int hashCode() {
//  return firstName.hashCode();
//}

}


这程序中,开始我并没有重写Name的hashCode方法,结果如注释所示,大家可以看到n1和n2这两个对象相同(equals)但它们的hashcode不同。所以并没有删除New Name("f1","l1")。
这里就跟上面说的矛盾了
1、如果两个对象相同,那么它们的hashCode值一定要相同;

当我重写hashCode方法后,返回firstName.hashCode()强行使n1和n2的hashcode的返回值相同后,就成功删除了
New Name("f1","l1")这种方法使我不能接受!
发现一个问题,如果不重写Name的toString方法,直接打印n1的话@后面的就是n1的hashcode值的十六进制!
麻烦高手解答小弟疑惑!
不慎感激!



楼主搞清楚了这段话:对象相同和对象相等时两个不同的概念 对象相同(先不谈string,因为他重写了equals方法)如:Name a1=new Name("f1","n1")和 Name a2=new Name("f2","n2")他们叫相等,不叫相同,而Name a3=a1,这叫 a3和a1相同,这里的前提是你没有重写equals方法,因为这样你就调用的是object里面的equals方法,object里面的equals方法比较的是两个对象的引用地址是否相等,那当然a1和a2的引用的地址不相等了,因为new了2个空间嘛,而a3和a1是指向同一对象的,所以是相等的。  然而对于String来说由于他重写了equals方法,他的equals比较的是2个对象的值是否相等。所以,String S1=new String(“w”)和String s2=new String("w")euqals是相等的,再记住那几句话(Java对于eqauls方法和hashCode方法是这样规定的: 
      1、如果两个对象相同,那么它们的hashCode值一定要相同; 
      2、如果两个对象的hashCode相同,它们并不一定相同 )
然后你再结合这边文章看看答案

#16


1、如果两个对象相同,那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同 

这个应该是你应该去遵守的,而不是写出了一段代码出来发现和你的预期不一致的时候,你想去推翻的。
只有你遵守了这个约定的时候,Collection和Set等数据结构才可能给你正确的结果。

#17


很多人都把String当成一个特例,其实不是的,大家有兴趣可以去看看String类的源代码就明白了。
Object类中,hashcode方法返回的就是内存地址,所以自定义一个类,如果没重写hashcode方法,则返回的和Object对象一样,是内存地址。
对于String类来说,因为业务的需要,通常情况下大家认为字符串值相等则是相同的对象,所以在String中重写了hashcode方法,返回的是和字符串值相关的一个整型值。
对象往SET集中加入值的时候的判断,我想楼主可以突略了一个最大的问题,理解上也是有校大偏差。就是SET集的实现都是基于哈希桶的算法的。
关于哈希桶简单的原理可以描述为:比如一个HashSet开了10000这么大空间的内存区,HashSet会在使用时把这个内存区分成10个小区(假设,实际上区数是2的N次方), 要往这个HashSet里加对象的时候,首先会判断加入的对象的hashcode值,我们都知道这个值是整型的,取到了这个值后除以区数,也就是除以10,得到作数是几就加入到十个区里的编号为几的区里面。当然要从Set集里取对象也是一样,首先判断要取对象的hashcode值,然后除以10,余数是几,就到编号为几的分区里去找相应的对象。

#18


引用 17 楼 liboofsc 的回复:
很多人都把String当成一个特例,其实不是的,大家有兴趣可以去看看String类的源代码就明白了。
Object类中,hashcode方法返回的就是内存地址,所以自定义一个类,如果没重写hashcode方法,则返回的和Object对象一样,是内存地址。
对于String类来说,因为业务的需要,通常情况下大家认为字符串值相等则是相同的对象,所以在String中重写了hashcode方法,返回的……
String重写hashcode之后,返回的hashcode实际上是ASCII码

推荐阅读
author-avatar
mobiledu2502886131
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有