有许多人学了很长时间的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用于判断对象的地址是否相同,而不是值。
两个不同的对象,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是不同的。
Name n2=new Name("f1","n1");
n1和n2不是两个相同的对象,只是他们的值相同。地址是不同的,所以equal相同,hashcode是不同的。
#3
可能我没说清楚我的问题,那s1和s2也是不同的对象为什么它们的hashcode的值相同呢?
#4
equals()相等的两个对象,hashcode()一定相等;
反过来:hashcode()不等,一定能推出equals()也不等;
hashcode()相等,equals()可能相等,也可能不等。
反过来:hashcode()不等,一定能推出equals()也不等;
hashcode()相等,equals()可能相等,也可能不等。
#5
如果你打印一个对象,不重些toString()方法,它就会调用默认你的toString()打印这个些的类名加这个这个类名的十六十六进制。
#6
理解存在偏差。
“要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是 Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。”
Java实现者怎么可能笨到这种地步。内部怎么实现那可是一门深奥的学问呢,设计很多高级的数据结构。否则还要用它提供的Collection干什么!自己也写一个呗。
“于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。”
这个理解确实荒唐,按这种说法Hash在不同的电脑上对同一个字符串难道还会返回不同的值,移植性从何谈起。
“要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是 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()。
有不完整或有疑义的地方,各位补充、探讨。
感谢楼主!
我举例子的时候,因为偷懒,不恰当的使用了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了。
大家来看看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相同.
java中的String类比较特殊,他和普通类的存储方式不同,他存储时自动进行优化,上面的s1和s2由于内容相同,所以占用一块内存,故其hashcode相同.
#10
看来很多人都有这个疑惑,s1和s2并不占用一块内存吧,它们都是new出来的,不可能占用同一块内存
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的就相等。与是否==无关。请大家仔细研究下这个问题!
待解决。。。
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方法,代码如下:
String类中的hashCode()的实现:
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
* <code>java.util.Hashtable</code>.
* <p>
* The general contract of <code>hashCode</code> is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the <tt>hashCode</tt> method
* must consistently return the same integer, provided no information
* used in <tt>equals</tt> 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.
* <li>If two objects are equal according to the <tt>equals(Object)</tt>
* method, then calling the <code>hashCode</code> method on each of
* the two objects must produce the same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the <tt>hashCode</tt> 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.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class <tt>Object</tt> 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
* Java<font size="-2"><sup>TM</sup></font> 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
* <code>String</code> object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using <code>int</code> arithmetic, where <code>s[i]</code> is the
* <i>i</i>th character of the string, <code>n</code> is the length of
* the string, and <code>^</code> 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 方法都能调用。
System.identityHashCode 这个方法,无论该类是否重写过 hashCode 方法都能调用。
#13
学习啦。。谢谢啦
#14
支持7楼的回答,String是一个引用类型。
在创建对象的时候,是创建的一个引用。
而s1,s2他们的值都是一样的。所以2个对象引用的同一个文中说到的物理路径。因而他们的hashCode是相同。
在创建对象的时候,是创建的一个引用。
而s1,s2他们的值都是一样的。所以2个对象引用的同一个文中说到的物理路径。因而他们的hashCode是相同。
#15
楼主搞清楚了这段话:对象相同和对象相等时两个不同的概念 对象相同(先不谈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等数据结构才可能给你正确的结果。
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,余数是几,就到编号为几的分区里去找相应的对象。
Object类中,hashcode方法返回的就是内存地址,所以自定义一个类,如果没重写hashcode方法,则返回的和Object对象一样,是内存地址。
对于String类来说,因为业务的需要,通常情况下大家认为字符串值相等则是相同的对象,所以在String中重写了hashcode方法,返回的是和字符串值相关的一个整型值。
对象往SET集中加入值的时候的判断,我想楼主可以突略了一个最大的问题,理解上也是有校大偏差。就是SET集的实现都是基于哈希桶的算法的。
关于哈希桶简单的原理可以描述为:比如一个HashSet开了10000这么大空间的内存区,HashSet会在使用时把这个内存区分成10个小区(假设,实际上区数是2的N次方), 要往这个HashSet里加对象的时候,首先会判断加入的对象的hashcode值,我们都知道这个值是整型的,取到了这个值后除以区数,也就是除以10,得到作数是几就加入到十个区里的编号为几的区里面。当然要从Set集里取对象也是一样,首先判断要取对象的hashcode值,然后除以10,余数是几,就到编号为几的分区里去找相应的对象。
#18
String重写hashcode之后,返回的hashcode实际上是ASCII码
#1
你自己查得很明确了。
两个不同的对象,hashcode不同。
String a = new String("abc");
String b = new String("abc");
两个不同的对象,但是值相同。
hashcode用于判断对象的地址是否相同,而不是值。
两个不同的对象,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是不同的。
Name n2=new Name("f1","n1");
n1和n2不是两个相同的对象,只是他们的值相同。地址是不同的,所以equal相同,hashcode是不同的。
#3
可能我没说清楚我的问题,那s1和s2也是不同的对象为什么它们的hashcode的值相同呢?
#4
equals()相等的两个对象,hashcode()一定相等;
反过来:hashcode()不等,一定能推出equals()也不等;
hashcode()相等,equals()可能相等,也可能不等。
反过来:hashcode()不等,一定能推出equals()也不等;
hashcode()相等,equals()可能相等,也可能不等。
#5
如果你打印一个对象,不重些toString()方法,它就会调用默认你的toString()打印这个些的类名加这个这个类名的十六十六进制。
#6
理解存在偏差。
“要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是 Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。”
Java实现者怎么可能笨到这种地步。内部怎么实现那可是一门深奥的学问呢,设计很多高级的数据结构。否则还要用它提供的Collection干什么!自己也写一个呗。
“于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。”
这个理解确实荒唐,按这种说法Hash在不同的电脑上对同一个字符串难道还会返回不同的值,移植性从何谈起。
“要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是 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()。
有不完整或有疑义的地方,各位补充、探讨。
感谢楼主!
我举例子的时候,因为偷懒,不恰当的使用了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了。
大家来看看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相同.
java中的String类比较特殊,他和普通类的存储方式不同,他存储时自动进行优化,上面的s1和s2由于内容相同,所以占用一块内存,故其hashcode相同.
#10
看来很多人都有这个疑惑,s1和s2并不占用一块内存吧,它们都是new出来的,不可能占用同一块内存
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的就相等。与是否==无关。请大家仔细研究下这个问题!
待解决。。。
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方法,代码如下:
String类中的hashCode()的实现:
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
* <code>java.util.Hashtable</code>.
* <p>
* The general contract of <code>hashCode</code> is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the <tt>hashCode</tt> method
* must consistently return the same integer, provided no information
* used in <tt>equals</tt> 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.
* <li>If two objects are equal according to the <tt>equals(Object)</tt>
* method, then calling the <code>hashCode</code> method on each of
* the two objects must produce the same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the <tt>hashCode</tt> 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.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class <tt>Object</tt> 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
* Java<font size="-2"><sup>TM</sup></font> 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
* <code>String</code> object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using <code>int</code> arithmetic, where <code>s[i]</code> is the
* <i>i</i>th character of the string, <code>n</code> is the length of
* the string, and <code>^</code> 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 方法都能调用。
System.identityHashCode 这个方法,无论该类是否重写过 hashCode 方法都能调用。
#13
学习啦。。谢谢啦
#14
支持7楼的回答,String是一个引用类型。
在创建对象的时候,是创建的一个引用。
而s1,s2他们的值都是一样的。所以2个对象引用的同一个文中说到的物理路径。因而他们的hashCode是相同。
在创建对象的时候,是创建的一个引用。
而s1,s2他们的值都是一样的。所以2个对象引用的同一个文中说到的物理路径。因而他们的hashCode是相同。
#15
楼主搞清楚了这段话:对象相同和对象相等时两个不同的概念 对象相同(先不谈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等数据结构才可能给你正确的结果。
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,余数是几,就到编号为几的分区里去找相应的对象。
Object类中,hashcode方法返回的就是内存地址,所以自定义一个类,如果没重写hashcode方法,则返回的和Object对象一样,是内存地址。
对于String类来说,因为业务的需要,通常情况下大家认为字符串值相等则是相同的对象,所以在String中重写了hashcode方法,返回的是和字符串值相关的一个整型值。
对象往SET集中加入值的时候的判断,我想楼主可以突略了一个最大的问题,理解上也是有校大偏差。就是SET集的实现都是基于哈希桶的算法的。
关于哈希桶简单的原理可以描述为:比如一个HashSet开了10000这么大空间的内存区,HashSet会在使用时把这个内存区分成10个小区(假设,实际上区数是2的N次方), 要往这个HashSet里加对象的时候,首先会判断加入的对象的hashcode值,我们都知道这个值是整型的,取到了这个值后除以区数,也就是除以10,得到作数是几就加入到十个区里的编号为几的区里面。当然要从Set集里取对象也是一样,首先判断要取对象的hashcode值,然后除以10,余数是几,就到编号为几的分区里去找相应的对象。
#18
String重写hashcode之后,返回的hashcode实际上是ASCII码