赞
踩
哈希值就是:对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。更重要的是:这里的内存地址是JVM虚拟机虚拟出来的地址,并不是真实的物理内存地址。哈希值是通过哈希算法散列得来的,而地址值是通过是和物理层面有关,是系统分配的,是不存在相同的,而哈希值是可以通过强制手段设置为相同的(下面我们也会详细介绍怎么弄成相同的),也就是说哈希值是一种逻辑上的确保唯一性,而地址值就是物理上确保唯一性。
这么说你可能还是不理解,到底什么是逻辑上的,什么是物理上的?
那么请看一个例子:我们都知道,c++的地址是真实的物理内存地址,所以相同的输出地址的代码在不同的计算机上输出的值是不一样的,这就是物理上的地址,因为每台电脑的内存使用情况都是千差万别的,所以地址是不一样的也就不奇怪了。
但是:在java中,一段相同的代码输出的hashCode绝对是一样的。可以亲自尝试一下。
System.out.println("hello".hashCode());//哈希码为99162322
这就说明一个问题:java里的hashCode只是逻辑上的,是我们jvm虚拟机通过特定的算法计算出来的,所以每台电脑上相同的语句的hashCode都是一样的, 这就是逻辑上的。
哈希码
在Java中,哈希码代表了对象的一种特征,例如我们判断某两个字符串是否==,如果其哈希码相等,则这两个字符串是相等的。其次,哈希码是一种数据结构的算法。常见的哈希码的算法有:
1:Object类的hashCode.返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。
2:String类的hashCode.根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串内容相同,返回的哈希码也相同。
3:Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integer i1=new Integer(100),i1.hashCode的值就是100 。由此可见,2个一样大小的Integer对象,返回的哈希码也一样。
上面提到了所谓的hashCode,那么什么是hashCode?
hashCode即散列码。散列码是用一个int值来代表对象,它是通过将该对象的某些信息进行转换而生成的。
补充说明:
散列函数(或散列算法,又称哈希函数,英语:Hash Function)是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums,或hashes)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表。好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理中,不抑制冲突来区别数据,会使得数据库记录更难找到。
Object类中默认的hashCode方法如下。
public native int hashCode();
这是一个本地方法,不同的虚拟机有不同的实现(具体实现自己看虚拟机源码哈)。Object默认的hashCode是根据对象的内存地址转化而来的,它是唯一的。
我们可以在自己的类中覆盖hashCode方法,但我们可以使用System.identityHashCode(Object x)方法返回默认的hashcode,无论对象是否覆盖默认的hashcode。
hashCode方法主要是为了给诸如HashMap这样的哈希表使用。
设计hashcode最重要的因素是:对同一个对象调用hachCode()应该产生同样的值(前提是对象的信息没有被改变)。
根据上面的介绍,我们知道了hashCode是一个方法,通过hashCode我们可以得到一个返回的哈希值,根据这个哈希值我们就可以来判断这个对象的内存地址是否相同,从而可以变相的的出这个对象的内容是否相等。
哈希值的一大作用就是:查重和去重。例如我们的Set集合就用到了hashCode来防止对象出现重复。
上面我们说到了,哈希值可以用来判断两个对象的内容是否相等,其实这个说法是不太准确的,列如在我们的String类中就可能会出现以下的情况
- // str1和str3的字符串相同。
- String str1="张三";
- String str2="李四";
- String str3="张三";
-
- System.out.println(str1.hashCode());//774889
- System.out.println(str2.hashCode());//842061
- System.out.println(str3.hashCode());//774889
-
- //可以看出,只要是字符串相同,hashCode也相同
但是情况一定是这样的吗?
答案是否定的,因为会出现哈希冲突。
-
- System.out.println("重地".hashCode());//1179395
-
- System.out.println("通话".hashCode());//1179395
-
- 可以看出,出现了字符串不同但是哈希值依然相同的情况,这说明比较字符串的内容还是equals更加可靠,只比较hashCode是不太靠谱的
通过以上的列子我们不难看出,在比较字符串的内容时,我们hashCode方法是存在一定问题的,而我们常用的比较字符串内容的方法是我们的equals方法,那么他们浪着之间又有什么联系呢?
这两个方法都是 Object 的方法,意味着 若一个对象在没有重写 这两个方法时,都会默认采用 Object 类中的方法实现,它们的关系为:
1、如果两个对象通过equals()方法比较相等,那么这两个对象的hashCode一定相同。
2、如果两个对象hashCode相同,不能证明两个对象是同一个对象(不一定相等),只能证明两个对象在散列结构中存储在同一个地址(不同对象hashCode相同的情况称为hash冲突)。
具体规则如下:
equals相等,hashcode一定相等。
equals不等,hashcode不一定不等。
hashcode不等,equals一定不等。
hashcode相等,equals不一定相等。
所以说判断两个元素是否相等:先通过hashcode来比较,如果hashcode相等,那么就用equals方法来比较两个对象是否相等。
(所以如果对象的equals方法被重写,那么对象的HashCode方法也尽量重写)
那么为什么对象的equals方法被重写时,hashCode方法也要尽量重写呢?
为了更容易通俗易懂,来个小故事缓解一下激动的心情~~
打个比方,一个名叫张三的人去住酒店,在前台登记完名字就去了99层100号房间,此时警察来前台找叫张三的这个人住在哪间房,经过查询,该酒店住宿的有50个叫张三的,需要遍历查询,查询起来很不方便。
那么就换另外一种登记方式,前台登记时登记身份证号,那么警察抢来查找时就需要比对每一个人的身份证效率也十分缓慢,就如同经过哈希算法进行计算后相同的hashcode值被分到了一个房间然后产生链表,链表查询效率非常慢,然后警察找的时候也会遇到问题。
那么只能换第三种登记方式了,前台登记时同时登记身份证号和名字,这样警察来找的时候同时按照两个条件去查,这样就能直接锁定要找的人在哪个房间。
在程序中:登记的前台好比哈希算法,名字是对比好比 equals 方法,身份证号的对比好比 hashcode 方法只有equals 和 hashcode 都满足的时候才能确保是同一个对象。
当我们重写一个类的 equals 方法时就应当连同重写 hashcode 方法,并且两个方法应满足:
1.一致性,即:当两个对象 equals 比较为 true,那么 hashcode 值应当相等,反之亦然,因为当两个对象hashcode 值相等,但是 equals 比较为 false,那么在 HashMap 中会产生链表,影响查询性能。
2.成对重写,即重写 equals 就应当重写 hashcode。
看完上面这个例子应该对equals方法和hashCode方法有了一定的理解,
接着,让我们再看看,Object源码中equals方法
如果没有重写hashCode和equals方法,比较的是地址值,因为Object的equals方法中使用是==
因为默认的equals方法是Object的方法,比较的是内存地址;而默认的hashcode方法返回的是对象zhi的内存地址转换成的一个整数,实际上指的的也是内存,两个方法可以理解为比较的都是内存地址,这在实际开发的过程中在hashmap或者hashset里如果不重写的hashcode和equals方法的话会导致我们存对象的时候,把对象存进去了,取的时候却取不到想要的对象,这时候就需要重写这两个方法了
如果重写hashcode和equals方法,则比较的是内容
例如;两个String字符串进行比较时,比较的是内容,因为String底层重写了equals方法进行内容的比较
通过本次的学习我们知道了任何对象,都有一个哈希值,这个哈希值是对象的一个整数数字表示. 对象的字符串表示是toString()方法。以及如何获取对象的哈希值,每一个对象都有一个方法,hashCode(),这个方法定义在了Object类中,所以每个对象都具有。
以及为什么要重写hashCode方法,hashCode方法和equals方法的联系!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。