equals()和hashCode()区别?html
-------------------------------------------------java
equals():反映的是对象或变量具体的值,即两个对象里面包含的值--多是对象的引用,也多是值类型的值。
程序员
hashCode():计算出对象实例的哈希码,并返回哈希码,又称为散列函数。根类Object的hashCode()方法的计算依赖于对象实例的D(内存地址),故每一个Object对象的hashCode都是惟一的;固然,当对象所对应的类重写了hashCode()方法时,结果就大相径庭了。算法
之因此有hashCode方法,是由于在批量的对象比较中,hashCode要比equals来得快,不少集合都用到了hashCode,好比HashTable。
两个obj,若是equals()相等,hashCode()必定相等。
两个obj,若是hashCode()相等,equals()不必定相等(Hash散列值有冲突的状况,虽然几率很低)。
因此:
能够考虑在集合中,判断两个对象是否相等的规则是:
第一步,若是hashCode()相等,则查看第二步,不然不相等;
第二步,查看equals()是否相等,若是相等,则两obj相等,不然仍是不相等。编程
一、首先equals()和hashcode()这两个方法都是从object类中继承过来的。
equals()是对两个对象的地址值进行的比较(即比较引用是否相同)。
hashCode()是一个本地方法,它的实现是根据本地机器相关的。
二、Java语言对equals()的要求以下,这些要求是必须遵循的:
A 对称性:若是x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
B 反射性:x.equals(x)必须返回是“true”。
C 类推性:若是x.equals(y)返回是“true”,并且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
D 一致性:若是x.equals(y)返回是“true”,只要x和y内容一直不变,无论你重复x.equals(y)多少次,返回都是“true”。
任何状况下,x.equals(null),永远返回是“false”;x.equals(和x不一样类型的对象)永远返回是“false”。
三、equals()相等的两个对象,hashcode()必定相等;
反过来:hashcode()不等,必定能推出equals()也不等;
hashcode()相等,equals()可能相等,也可能不等。 数组
为何选择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.
可能通过上面理论的讲一下你们都迷糊了,我也看了以后也是似懂非懂的。下面我举个例子详细说明下。
list是能够重复的,set是不能够重复的。那么set存储数据的时候是怎样判断存进的数据是否已经存在。使用equals()方法呢,仍是hashcode()方法。
假如用equals(),那么存储一个元素就要跟已存在的全部元素比较一遍,好比已存入100个元素,那么存101个元素的时候,就要调用equals方法100次。
但若是用hashcode()方法的话,他就利用了hash算法来存储数据的。
这样的话每存一个数据就调用一次hashcode()方法,获得一个hashcode值及存入的位置。若是该位置不存在数据那么就直接存入,不然调用一次equals()方法,不相同则存,相同不存。这样下来整个存储下来不须要调用几回equals方法,虽然多了几回hashcode方法,但相对于前面来说效率高了很多。
为何要重写equals方法?
-------------------------------------------
由于Object的equal方法默认是两个对象的引用的比较,意思就是指向同一内存,地址则相等,不然不相等;若是你如今须要利用对象里面的值来判断是否相等,则重载equal方法。
说道这个地方我相信不少人会有疑问,相信你们都被String对象的equals()方法和"=="纠结过一段时间,当时咱们知道String对象中equals方法是判断值的,而==是地址判断。
那照这么说equals怎么会是地址的比较呢?
那是由于实际上JDK中,String、Math等封装类都对Object中的equals()方法进行了重写。
咱们先看看Object中equals方法的源码:
1
2
3
|
public
boolean
equals(Object obj) {
return
(
this
== obj);
}
|
咱们都知道全部的对象都拥有标识(内存地址)和状态(数据),同时“==”比较两个对象的的内存地址,因此说使用Object的equals()方法是比较两个对象的内存地址是否相等,即若object1.equals(object2)为true,则表示equals1和equals2其实是引用同一个对象。虽然有时候Object的equals()方法能够知足咱们一些基本的要求,可是咱们必需要清楚咱们很大部分时间都是进行两个对象的比较,这个时候Object的equals()方法就不能够了,因此才会有String这些类对equals方法的改写,依次类推Double、Integer、Math。。。。等等这些类都是重写了equals()方法的,从而进行的是内容的比较。但愿你们不要搞混了。
改写equals时老是要改写hashcode
-------------------------------------------
java.lnag.Object中对hashCode的约定:
1. 在一个应用程序执行期间,若是一个对象的equals方法作比较所用到的信息没有被修改的话,则对该对象调用hashCode方法屡次,它必须始终如一地返回同一个整数。
2. 若是两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
3. 若是两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不一样的整数结果。但若是能不一样,则可能提升散列表的性能。
根据上一个问题,实际上咱们已经能很简单的解释这一点了,好比改写String中的equals为基于内容上的比较而不是内存地址的话,那么虽然equals相等,但并不表明内存地址相等,由hashcode方法的定义可知内存地址不一样,没改写的hashcode值也可能不一样。因此违背了第二条约定。
又如new一个对象,再new一个内容相等的对象,调用equals方法返回的true,但他们的hashcode值不一样,将两个对象存入HashSet中,会使得其中包含两个相等的对象,由于是先检索hashcode值,不等的状况下才会去比较equals方法的。
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的性能。即“分散原则”。
掌握了这两条原则,你就可以用好HashMap编写本身的程序了。不知道你们注意没有, java.lang.Object中提供的三个方法:clone(),equals()和hashCode()虽然很典型,但在不少状况下都不可以适用,它们只是简单的由对象的地址得出结果。这就须要咱们在本身的程序中重写它们,其实java类库中也重写了千千万万个这样的方法。利用面向对象的多态性——覆盖,Java的设计者很优雅的构建了Java的结构,也更加体现了Java是一门纯OOP语言的特性。
Java提供的Collection和Map的功能是十分强大的,它们可以使你的程序实现方式更为灵活,执行效率更高。但愿本文可以对你们更好的使用HashMap有所帮助。