`
xinklabi
  • 浏览: 1591916 次
  • 性别: Icon_minigender_1
  • 来自: 吉林
文章分类
社区版块
存档分类
最新评论

hashCode,equals 和 ==(看了N遍的东西)

    博客分类:
  • Java
 
阅读更多

先说equals和==.

简单的说,==是判断两个变量或实例是不是指向同一个内存空间,equals是判断两个变量或实例所指向的内存空间的值是不是相同。

例:

String s1, s2;
s1 = "abcdefg";
s2 = "abcdefg";
System.out.println(s1.equals(s2));
System.out.println(s1==s2);
结果打印出两个true。

java虚拟机有一个字符串池,这样声明的字符串都存放在这个池里,如果以后声明的字符串和先前声明的一样,那么就不建立新的字符串,而是两个字符串变量指向相同的引用了。

如果用String s1=new String("abcdefg")和
String s2=new String("abcdefg")来声明的话,这样来比较用==号的话那就不一样了,因为是指向不同的地址。说到这里,就需要补充一下JAVA里String这个数据类型。

写过C++的代码人其实很容易理解char* str = "log something"; "log something"在编译时会把它放在常量区,str只是指到该常量的一个指针。其实,Java中的存也在类似的情况,只不过不是放在常量区,Java中叫常量池,目的是一致的。常量池中常量内容由ClassLoader加载到JVM中。

是这样的吗?下面我们来做个测试。
String str0=”something”;
String str1
=”something”;
String str2
=”some” + “thing”;
System.out.println( str0
==str1 );
System.out.println( str0
==str2 );
运行的结果是:
true
true

因为例子中的str0和str1中的”something”都是字符串常量,它们在编译期就被确定了,所以str0==str1为true;而”some”和”thing”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以str2也同样在编译期就被解析为一个字符串常量,所以str2也是常量池中” something”的一个引用。

于是:str0==str1==str2 ,再套用C++的观点,str0,str1,str2都有指到同一块内存的内容。

我们要注意的是new String()产生对象不是在放在常量池中,在编译期无法知道String的数据内容,在运行期还能知到。下面我们再来做一个测试。
String str0=new String(”something”);
String str1
=new String(”something”);
String str2
=”some” +new String(“thing”);
System.out.println( str0
==str1 );
System.out.println( str0
==str2 );
System.out.println( str1
==str2 );
运行的结果是:
false
false
false


这是因为每个字符串为一个对象,比较是他们的引用地址,在堆分配的地址肯定是不一样的。于是:我们在表达一个字符串常量是,不用使用final String str0=new String(”something”);,这是多此一举,final也是多余的,那是因为String来就是一个final对象,一量生成,不可修改,即使String str0= str0.replace("o", "e")之类的写法,也会产量临时变量,str0再指向临时变量的引用。

关于String是不可变的
  这一说又要说很多,只要知道String的实例一旦生成就不会再改变了,比如说:String str=kv+ill+” “+ans;
就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” “ 生成 ”kvill “存在内存中,最后又和生成了”kvill ans;并把这个字符串的地址赋给了str,就是因为String的“不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原因了,因为StringBuffer是可改变的。

 

在java的Object类中“equals()”的实现如下:

1 public boolean equals(Object obj) {
2 return (this == obj);
3 }


我们可以看到在Object中“equals()”的实现指示简单的使用了“==”也就是之判断了,两个对象的引用相等。这也就是为什么我们在使用“equals()”时必须重写这个方法的原因了。

在jdk的帮助文档中,对于“equals()”和“HashCode()”有这样的说法,如果两个对象使用equals()返回为true,则使用HashCode()必然返回的值必然相等,反之则不一定啊。

 

 

 

为什么HashCode对于对象是如此的重要?

  一个对象的HashCode就是一个简单的Hash算法的实现,虽然它和那些真正的复杂的Hash算法相比还不能叫真正的算法,它如何实现它,不仅仅是程序员的编程水平问题,而是关系到你的对象在存取是性能的非常重要的关系.有可能,不同的HashCode可能会使你的对象存取产生,成百上千倍的性能差别。

  我们先来看一下,在JAVA中两个重要的数据结构:HashMap和Hashtable,虽然它们有很大的区别,如继承关系不同,对的约束条件(是否允许null)不同,以及线程安全性等有着特定的区别,但从实现原理上来说,它们是一致的.所以,我们只以Hashtable来说明:

  在java中,存取数据的性能,一般来说当然是首推数组,但是在数据量稍大的容器选择中,Hashtable将有比数组性能更高的查询速度.具体原因看下面的内容。

  Hashtable在存储数据时,一般先将作为key的对象的HashCode和0x7FFFFFFF做与操作,因为一个对象的HashCode可以为负数,这样操作后可以保证它为一个正整数.然后以Hashtable的长度取模,得到值对象在Hashtable中的索引。

  index = (o.hashCode() & 0x7FFFFFFF)%hs.length;这个值对象就会直接放在Hashtable的第index位置,对于写入,这和数组一样,把一个对象放在其中的第index位置,但如果是查询,经过同样的算法,Hashtable可以直接通过key得到index,从第index取得这个值对象,而数组却要做循环比较.所以对于数据量稍大时,Hashtable的查询比数据具有更高的性能。

  虽然不同对象有不同的hashcode,但不同的hashCode经过与长度的取余,就很可能产生相同的index。

  极端情况下会有大量的对象产生一个相同的索引.这就是关系Hashtable性能问题的最重要的问题:

  Hash冲突。

  常见的Hash冲突是不同key对象最终产生了相同的索引,而一种非常甚至绝对少见的Hash冲突是,如果一组对象的个数大过了int范围,而HashCode的长度只能在int范围中,所以肯定要有同一组的元素有相同的HashCode,这样无论如何他们都会有相同的索引.当然这种极端的情况是极少见的,可以暂不考虑,但是对于同的HashCode经过取模,则会产中相同的索引,或者不同的对象却具有相同的HashCode,当然具有相同的索引。

  事实上一个设计各好的HashTable,一般来说会比较平均地分布每个元素,因为Hashtable的长度总是比实际元素的个数按一定比例进行自增(装填因子一般为0.75)左右,这样大多数的索引位置只有一个对象,而很少的位置会有几个元素.所以Hashtable中的每个位置存放的是一个链表,对于只有一个对象是位置,链表只有一个首节点(Entry),Entry的next为null.然后有hashCode,key,属性保存了该位置的对象的HashCode,key和(对象本身),如果有相同索引的对象进来则会进入链表的下一个节点.如果同一个索引中有多个对象,根据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对于我们的程序性能有着生要的影响,在程序设计时应该时时加以注意。

分享到:
评论

相关推荐

    equals与hashCode方法讲解

    equals 方法和 hashCode 方法是 Java 语言中两个重要的方法,它们都是在 Object 类中定义的。equals 方法用于比较两个对象是否相等,而 hashCode 方法用于返回对象的哈希码。 在 Java 的 Object 类中,equals 方法...

    Java容器集合(equals 和 hashCode+基础数据结构+ArrayList+Vector和LinkedList)

    Java容器集合(equals和hashCode+基础数据结构+ArrayList+Vector和LinkedList) Java容器集合是Java中的一种基础数据结构,用于存储和管理数据。其中,equals和hashCode方法是Java容器集合中两个非常重要的方法,...

    java中hashcode()和equals()的详解.docx

    在Java中,正确实现`equals()`和`hashCode()`方法对于保证程序的逻辑正确性和性能至关重要。开发者需要确保这两个方法的一致性和效率,特别是在自定义类时,更要注意遵循上述提到的原则。只有这样,才能充分利用Java...

    如何在IDEA中对 hashCode()和 equals() 利用快捷键快速进行方法重写

    在Java编程中,`equals()`和`hashCode()`方法是Object类中的两个重要方法。当我们创建自定义类并将其对象放入集合(如HashSet)时,往往需要重写这两个方法以确保集合能够正确地处理这些对象。IntelliJ IDEA,作为一...

    Java 33道面试题及答案.docx

    Java 面试题中 涉及到 Java 基础知识、JDK 和 JRE 的区别、== 和 equals 的区别、equals 方法的本质、hashCode() 和 equals() 的关系等知识点,希望这些知识点能够帮助大家更好地理解 Java 编程语言。

    深入理解Java中HashCode方法

    hashCode方法的实现方式有多种,String类的hashCode方法就是一个典型的例子,它使用数学表达式s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]来计算hashCode值,其中s[i]是字符串的第i个字符,n是字符串的长度。...

    Java基础加强_ArrayList_HashSet的比较及Hashcode分析

    它不允许有重复元素,通过重写对象的equals()和hashCode()方法来确定元素是否相等和在哈希表中的位置。HashSet在插入新元素时会计算其Hashcode,根据Hashcode将元素放入哈希表的特定位置,以达到快速查找的目的。当...

    Java17道面试题及答案

    hashCode()和 equals()的关系 两个对象的 hashCode()相同,equals()不一定 true。代码示例: String str1 ="通话"; String str2 ="重地"; System.out.println(str1.hashCode() == str2.hashCode());//true System....

    Java 最常见的 208 道面试题.pdf

    hashCode() 和 equals() 的关系 两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?不对,两个对象的 hashCode() 相同,equals() 不一定 true。代码示例: ```java SString s1 = new String("老王"); ...

    java中的哈希算法和hashcode深入讲解1

    从实现上来说,java是借助hashcode()方法和equals()方法来实现判断元素是否已经存在的。当我们向HashMap中插入元素A时,首先,调用hashcode()方法,判断元素A在容器中是否已经存在。如果A元素的hashcode值在HashMap...

    java equals函数用法详解

    同时,如果重写了 `equals()`,通常也需要重写 `hashCode()` 方法,以保持它们之间的合同关系。 2. **一致性**:`equals()` 方法应该始终返回一致的结果,也就是说,如果对象A等于对象B,那么在任何时间点调用 `...

    快手真实面试题,不多,但真实

    6. hashCode 的作用和哈希冲突的解决 7. Java 的访问修饰符默认的是什么? 8. static 的用法和静态方法加锁的效果 9. Java 四种引用类型:强引用、软引用、弱引用、幻象引用 10. 悲观锁和乐观锁的区别 二、Android ...

    java利用delayedQueue实现本地的延迟队列

    Java 利用 DelayedQueue 实现本地的延迟队列 DelayedQueue 是 Java 中的一种特殊的阻塞队列,它...使用 DelayQueue 可以简化我们的业务逻辑,满足我们在业务中的一些需求,它能够使我们的代码更加简洁、易读和维护。

    2021-2022计算机二级等级考试试题及答案No.16188.docx

    1. 对象的`equals()`和`hashCode()`方法:在Java中,`equals()`方法用于比较对象的内容是否相等,而`hashCode()`方法则返回对象的哈希码,用于对象在哈希表(如HashSet或HashMap)中的快速查找。如果两个对象`equals...

    EqualsHashCodeToStringBuilders-Java8:等于,使用Lambdas,Java8的HashCode和ToString Builder

    等于,HashCode和ToString构建器使用Java8实现Equals,HashCode和ToString Builder。建立资讯使用Gradle 2.0进行构建。 请不要签入Eclipse或Intellij或任何特定于IDE的文件。 对于Idea或Eclipse,可以使用以下命令...

    sesvc.exe 阿萨德

    如果桶是一个链表则需要遍历判断里面的 hashcode、key 是否和传入 key 相等,如果相等则进行覆盖,并返回原来的值。 如果桶是空的,说明当前位置没有数据存入;新增一个 Entry 对象写入当前位置。 void addEntry(int...

    生成Eclipce的Java插件Jenerate.zip

    \r\n \r\n \r\n \r\n \r\n \u8f6f\u4ef6\u9996\u9875\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\nwindow.changyan.api.config({\r\nappid: 'cysXjLKDf', conf: 'prod_33c27aefa42004c9b2c12a759c851039...

    第8天(集合【LinkedList、HashSet、Collection集合体系】)v201703103

    \n - **HashSet存储自定义元素**:HashSet可以存储任何类型的对象,包括自定义对象,但要求对象必须实现`hashCode()`和`equals()`方法,以便于计算哈希码并判断元素唯一性。\n\n4. **判断集合元素唯一性原理**\n 在...

Global site tag (gtag.js) - Google Analytics