hashcode的作用就是为了快速查找集合中是否存在重复元素。它是配合euqals方法使用的。
先简要介绍equals方法:在object中此方法比较两个对象的地址是不是相等。api中的一些类重写了此方法,如String重写了此方法(但StringBuffer没有重写此方法),比较的是两个字符串的内容是不是相等。因此我们在定义一个对象的时候也可以重写equals方法,按照我们的原则来定义。
再看一个问题:
为什么重写了equals方法需要重写hashcode方法?
答:为了更快判断集合中是否已存在某对象。对于链表和数组,如果我们要查找某个元素是否已经存在,我们需要遍历数组和链表,直到找到这个元素位置,这样很耗时间(事实上,链表和数组在加入一个元素时也没有判断它是否存在,因此可以有重复)。如果我们使用哈希表,我们就可以根据这个元素的hashcode方法和equals方法快速判断集合中是否已存在某对象。下面对此进行描述:
哈希表由哈希表元地址和哈希表元组成,是一个链表的数组。在java中,哈希码就是根据对象的hashcode方法产生的哈希码(hashcode一般是根据对象的成员变量生成哈希码)。一般而言,不同的对象有不同的hashcode,但也可能不同的对象拥有同一个哈希码。但是哈希表元地址是如何与哈希码匹配的呢?举个例子吧:
假设现在有16个哈希表元,那么哈希表元地址则为1到16,然后现在有个对象的哈希码为329,329%16=9,则这个对象就存储在哈希表元地址为9的哈希表元上。假设现在又有一个对象的哈希码为25,25%16=9,好的,由于哈希地址为9的哈希表元上已经有元素了,这时新存进来的对象就要遍历这个哈希表元(不是全部元素),通过equals方法判断这个哈希表元上是否已经存在相同对象,如果不存在,就继续存储在哈希表元的后面。
总结如下:每要添加一个对象,都是先获取它的hashcode值,然后一次就匹配了哈希表元地址,如果哈希表元上还没有存储对象,就直接存储,如果已经有了就遍历哈希表元(通过equals方法判断是否有相同对象存在),如果不存在相同对象就存储在哈希表元后面。
[img]http://dl.iteye.com/upload/attachment/234138/136d9844-8044-3d3f-aaac-06f0adaad78c.png[/img]
现在我们来看看HashSet的add方法,当add一个对象时,会根据这个对象生成的hashcode值快速到链表数组中匹配对应的哈希表元,如果哈希表元为空的,就可以把对象插入哈希表元中,然后返回true。如果哈希表元中已经有元素,这时就要遍历这个哈希表元(记住,只是遍历其中一个哈希表元,而不是集合中的所有元素,可能就一两个元素),通过equals方法来判断是否已经有相同的对象存在,如果有则返回false,如果没有则找空间存贮对象并链接到这个哈希表元的后面。
看完这里应该就明白了为什么重写equals方法还要重写hashcode方法,就是为了快速查找集合中是否存在重复元素。
记住一个原则:如果a.equals(b)为true时,a和b必须拥有相同的hashcode。
举个例子说明:
假设现在new出两个Person对象:
我们上述实现的equals方法和hashcode方法必须满足下面条件:
1. p1.equals(p2)为true时,p1和p2必须拥有相同的哈希码。
2. p1和p2拥有相同的哈希码时,p1.equals(p2)不一定为true。
对于第二点我们可以设想一下:
因此p1和p2的hashcode相同,但p1.equals(p2)为false。
hashcode() 方法,在object 类中定义如下:
public native int hashCode(); <完>
说 明是一个本地方法,它的实现是根据本地机器相关的 。当然我们可以在自己写的类中覆盖hashcode() 方法,比如String 、Integer 、 Double 。。。。等等这些类都是覆盖了hashcode() 方法的。例如在String 类中定义的hashcode() 方法如下:
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;
}
解释一下这个程序(String 的API 中写到):
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
使用 int 算法,这里 s[i] 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希码为 0 。)
首先,想要明白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 集合中。同时,增加新元素的效率会大大下降。
先简要介绍equals方法:在object中此方法比较两个对象的地址是不是相等。api中的一些类重写了此方法,如String重写了此方法(但StringBuffer没有重写此方法),比较的是两个字符串的内容是不是相等。因此我们在定义一个对象的时候也可以重写equals方法,按照我们的原则来定义。
再看一个问题:
为什么重写了equals方法需要重写hashcode方法?
答:为了更快判断集合中是否已存在某对象。对于链表和数组,如果我们要查找某个元素是否已经存在,我们需要遍历数组和链表,直到找到这个元素位置,这样很耗时间(事实上,链表和数组在加入一个元素时也没有判断它是否存在,因此可以有重复)。如果我们使用哈希表,我们就可以根据这个元素的hashcode方法和equals方法快速判断集合中是否已存在某对象。下面对此进行描述:
哈希表由哈希表元地址和哈希表元组成,是一个链表的数组。在java中,哈希码就是根据对象的hashcode方法产生的哈希码(hashcode一般是根据对象的成员变量生成哈希码)。一般而言,不同的对象有不同的hashcode,但也可能不同的对象拥有同一个哈希码。但是哈希表元地址是如何与哈希码匹配的呢?举个例子吧:
假设现在有16个哈希表元,那么哈希表元地址则为1到16,然后现在有个对象的哈希码为329,329%16=9,则这个对象就存储在哈希表元地址为9的哈希表元上。假设现在又有一个对象的哈希码为25,25%16=9,好的,由于哈希地址为9的哈希表元上已经有元素了,这时新存进来的对象就要遍历这个哈希表元(不是全部元素),通过equals方法判断这个哈希表元上是否已经存在相同对象,如果不存在,就继续存储在哈希表元的后面。
总结如下:每要添加一个对象,都是先获取它的hashcode值,然后一次就匹配了哈希表元地址,如果哈希表元上还没有存储对象,就直接存储,如果已经有了就遍历哈希表元(通过equals方法判断是否有相同对象存在),如果不存在相同对象就存储在哈希表元后面。
[img]http://dl.iteye.com/upload/attachment/234138/136d9844-8044-3d3f-aaac-06f0adaad78c.png[/img]
现在我们来看看HashSet的add方法,当add一个对象时,会根据这个对象生成的hashcode值快速到链表数组中匹配对应的哈希表元,如果哈希表元为空的,就可以把对象插入哈希表元中,然后返回true。如果哈希表元中已经有元素,这时就要遍历这个哈希表元(记住,只是遍历其中一个哈希表元,而不是集合中的所有元素,可能就一两个元素),通过equals方法来判断是否已经有相同的对象存在,如果有则返回false,如果没有则找空间存贮对象并链接到这个哈希表元的后面。
看完这里应该就明白了为什么重写equals方法还要重写hashcode方法,就是为了快速查找集合中是否存在重复元素。
记住一个原则:如果a.equals(b)为true时,a和b必须拥有相同的hashcode。
举个例子说明:
public class Person {
private String name;
private int age;
@Override
public int hashCode() {
return age+name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Test3 other = (Test3) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
假设现在new出两个Person对象:
Person p1 = new Person();
Person p2 = new Person();
我们上述实现的equals方法和hashcode方法必须满足下面条件:
1. p1.equals(p2)为true时,p1和p2必须拥有相同的哈希码。
2. p1和p2拥有相同的哈希码时,p1.equals(p2)不一定为true。
对于第二点我们可以设想一下:
p1.age=24; p1.name.hashcode=109;
p2.age=27;p2.name.hashcode=106;
因此p1和p2的hashcode相同,但p1.equals(p2)为false。
hashcode() 方法,在object 类中定义如下:
public native int hashCode(); <完>
说 明是一个本地方法,它的实现是根据本地机器相关的 。当然我们可以在自己写的类中覆盖hashcode() 方法,比如String 、Integer 、 Double 。。。。等等这些类都是覆盖了hashcode() 方法的。例如在String 类中定义的hashcode() 方法如下:
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;
}
解释一下这个程序(String 的API 中写到):
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
使用 int 算法,这里 s[i] 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希码为 0 。)
首先,想要明白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 集合中。同时,增加新元素的效率会大大下降。