`
lvwenwen
  • 浏览: 956081 次
  • 性别: Icon_minigender_1
  • 来自: 魔都
社区版块
存档分类
最新评论

hashcode和equals

阅读更多
java中的Map一直是很多程序员广为使用的一种容器,关于collection以及Map之类的关系不再赘述,包括map并非collection的一个应用等等。
只是想记录一下很多人意识到但并未100%弄明白的一件事情,就是HashMap,HashTable,LinkedHashMap,中涉及到hashcode和equals的一些基本常识。

TIJ中对于这段有明确的讲解,但是可能相对专业(其实不然,很基础),所以字一多,很多人都看不下去了。从最基本的问题说起吧,

Map mp = new HashMap();
mp.put("1","A");
mp.put("2","B");
mp.put("3","C");

这是一个最基本的值设定。

Map中的内部类Map.Entry<K,V>机制是我们用key取得value的最根本的一个处理。
Map.Entry的作用来比作一个二维数组吧,注意只是类比
a[0][0]={"1","A"}
a[1][0]={"2","B"}
a[2][0]={"3","C"}
那么,如果我们想通过key来取得value的时候如何去做?
ok,循环,找到你要取的key,进而取得名称。

但是,如果map相当的大,假设我们的key是1到10000顺序put到map中,而我正好要取得key是10000的value,岂不是要循环10000次?当然,这里要纠正一个错误,HashMap不是linkedHashMap,所以你put的顺序和get出来的顺序是不同的。

言归正传,要取得key是已知的,但是位置是未知的。世界杯上贺炜解说巴西对朝鲜的比赛我还记忆犹新,对手强大不会令自己害怕,而人们对于未知事务才会恐惧。ok,一个基本存在每个系统中的map结构是不会成就一个未知的空洞的。所以,在map的机制中,引入了hashcode的概念。

首先,刚才说过了,所有的map的存贮都会依赖于Map.Entry<K,V>机制,所以,给定一个key后,调用map.get(key)方法的时候,一定是要循环Map.Entry,从而从相应的key取得相应的value。
据个例子:
我们的map有10000个项目(假定key是1到10000),那么就有10000个Map.Entry<K,V>组。
如果,我们给定一个key是555,需要得到相应的value,那么我们应该能够迅速到达这个key对应的Map.Entry<K,V>的前后,经过有限的几次循环就找到这个key,并且得到相应的value。补充一点,java中什么数据结构的循环检索速度最快?数组(详见TIJ)。基于以上,hashcode应运而生,hashcode的最基本原理就是:
当我们给map.put(key,value)的时候,通过key调用一个hash算法,从而得到一个数值,这个数值本身就是hashcode,这个hashcode作为一个数组的下标存在,数组中存放的就是我们的Map.Entry<K,V>。所以,当我们再一次调用map.get(key)的时候,会首先得到hashcode,然后迅速找到了数组相应的下标,马上又找到了里面的Map.Entry<K,V>,这样就符合了我们刚才的要求,迅速定位到key所对应的Map.Entry<K,V>去。

大概流程是:
map.put("1","A")

由上面的key="1"的"1"算出来,hashcode = 123(假设)
那么存在一个数组a[]
数组a[123]的位置,存放的就是Map.Entry<"1","A">

当我们调用
map.get("1")时
获得hashcode = 123
数组a[123]得到,马上获得Map.Entry<"1","A"> 从而用key="1"取得到value="A"

以上演示出了最基本的一个map存贮的过程过程。

至此,读者应该有一个简单的认识了吧,不过问题接踵而来。
第一,hashcode是如何计算出来的?
回答:看java相关的资料吧。
第二,计算出来hashcode当做下标?那数组要多大啊?
回答:数组是有界的。
第三,数组是有界的?那重复了怎么办?

好,第三个问题引入到下面的讨论中。
诚然,既然数组是有界的,那么计算hashcode势必会出现重复的情况(这个是依据算法的,一般来说好的算法可以最低限度的减少重复,并不绝对),出现重复有什么可怕?把相应的Map.Entry<key,value>顺序都排到这个下标底下去呗。刚才说了,一个好的算法可以最大限度的减少重复,即使出现了重复也是我们可以接收的范围之内,试想,一个数组下标下面就挂着5个Map.Entry<key,value>的话,最多需要循环5次而已嘛。

如果真的出现了重复,我们需要取得key对应的value,这时候需要循环,循环干什么?当然是一个个的通过Map.Entry<key,value>的getKey方法来得到key,看和我们查找用的key是否一致,如果一致的话,直接调用Map.Entry的getValue方法来获得value。

【看和我们查找用的key是否一致】,这句话很重要,引出了另一个雪藏的方法:equals。
大家都知道,equals是判断两个变量是否一致的一个方法,为什么说是判断两个变量是否一致呢?因为equals比较的是对象的地址,给我们造成假象的string 等等基本类型的equals方法早已被悄悄的覆盖过了,他们实际比较的都是数值,所以很多人也就一直活在假象中了。这里,Map.Entry调用getKey后,马上会调用我们自己的传递过来的key的equals方法,因为上面的例子传递的是一个String类型,那么equals方法是java现成的已被覆盖过的比较值一致性的equals方法,一致的话,getValue,不一致继续向下。

至此,一个map的get set过程基本结束,hashcode和equals方法的职责也基本上被曝光的一览无余。

好的,继续回答问题。

1 我们经常说,两个值的hashcode一样,值一定相等么?
回答:不一定啊,除非你的hash算法是100%唯一的。
如果是100%唯一的,这样连equals都省了。
其实像string int之类,不知道大家有没有做过实验,貌似是每个字符的hashcode都不一样的,是否string就是例外呢,hashcode可以唯一确定这个字符?答案是否定的。2^32= 4294967296。这个数值是java中的整形数值的极限,hashcode是一组整形数,2^32个,而string的字符串那就看使用者的想象力了,肯定是无限的,所以,字符串的hashcode势必会有重复的,只是,看运气了,也许有的人一辈子也碰不上,呵。

2 应该覆盖你的hashcode方法和equals方法
回答:嗯,java的多态成就了很多人的继承梦想,一个能用聚合关系或者用普通组合关系的处理方法都一定要用继承,造成了继承的泛滥——这是题外话。
什么时候应该覆盖你的hashcode方法和equals方法呢?回到概念上来说事。hashcode是用来查找你的Map.Entry<K,V>用的,我们的Map中有set和get方法对吧,我们在set的时候生成hashcode1,在get的时候生成hashcode2,那么我们的hashcode1一定要等于hashcode2的,否则set的时候放进去了,取的时候用hashcode2定位到了完全不一样的另一个数组下标,怎么可能再取得刚才放入的值呢。所以,如果我们的hashcode会存在这种变化的对象,我们一定要覆盖他们的hashcode方法。像刚才我们说的string int之类的就不用覆盖了,因为java已经帮我们覆盖好了,可以保证get和set的时候hashcode不会变化,而且equals也是对值得比较。而如果当我们使用一个对象,
比如:
class Studenst {
   private string name; // 姓名
   private int age; // 年龄
   private int no; // 学号
  
   // get set method 省略。。。
}
 
使用这个Student类作为map的key的时候,我们在实例化一个对象的时候,这个对象的hashcode是根据它的地址算出的,那么当我们又一次实例化这个对象的话,地址变化了,hashcode也就随着变化了。什么?你用的是单例模式?单例模式还会存在不同状态的对象可以往map里面放么。
言归正传,仔细观察上面的这个Student,我们发现其实学号一样的两个对象,我们可以判断为两个对象是同一个人,也可判断其“相等”。如此说来,我们应该针对这个对象的"no"来制定一个hash算法,计算出来的hashcode其实就是"no"的hashcode即可:
在Student类中定义hashcode方法:
    public int hashcode() {
        return this.no.hashcode();
    }

同理,对于Student对象比较equals的时候,其实我们比较的还是"no",那么我们也需要覆盖equals方法,在定位到存在复数个Map.Entry<K,V>的时候,可以循环判断用。

    public int equals(Object o) {
        return this.no == (Student)o.getNo();
    }
此时,Student类可以用来随意的set或者get了。

3 两个“值”equals返回true,那么hashcode肯定一致,如果两个值hashcode一致,equals不一定返回true.
回答:这个就不用解释了吧,上面已经很清楚了。

到现在为止,基本上Map的hashcode,equals等等的概念已经用非常白话的语言描述清楚了,这里没有任何晦涩的概念性的东西,都是用我的话来描述的,所以肯定易于理解,不过如果有概念性的错误或者引述不正确的地方,也希望大家指正,虚心接受。:)
分享到:
评论

相关推荐

    hashcode和equals方法

    equals()和hashcode()这两个方法都是从object类中继承过来的。当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法.

    java中hashcode和equals的详解.pdf

    Java 中的 hashCode 和 equals 方法详解 本文详细介绍了 Java 中的 hashCode 和 equals 方法,探讨了这两个方法的作用、实现机制和使用场景。通过对 hashCode 和 equals 方法的深入分析,我们可以更好地理解 Java ...

    hashcode和equals的分析

    ### hashCode和equals方法详解 #### 一、hashCode方法解析 在深入探讨`hashCode`方法之前,我们需要了解Java集合框架的基本概念。Java集合框架主要包括两大类集合:`List`和`Set`。 - **List**:这是一个有序集合...

    set接口经常用的hashCode和equals方法详解

    ### set接口中hashCode和equals方法详解 #### 一、引言 在Java编程语言中,`Set`接口作为集合框架的重要组成部分,在实现无重复元素的数据结构方面扮演着关键角色。为了确保元素的唯一性,`Set`接口依赖于对象的`...

    Java中hashCode和equals方法的正确使用

    在这篇文章中,我将告诉大家我对hashCode和equals方法的理解。我将讨论他们的默认实现,以及如何正确的重写他们。我也将使用Apache Commons提供的工具包做一个实现。  hashCode()和equals()定义在Object类中,这...

    HashCode相同equals不同的2位字符集合算法

    在Java编程语言中,`hashCode()` 和 `equals()` 是两个非常重要的方法,它们主要用于对象的比较和哈希表(如HashMap)的操作。标题提到的"HashCode相同equals不同的2位字符集合算法"涉及到的是一个特定场景:两个...

    关于hashCode()和equals()的本质区别和联系

    hashCode() 和 equals() 的本质区别和联系 Java 中的每个对象都有 hashCode() 和 equals() 方法,这两个方法的正确实现对于 Java 开发人员来说是非常重要的。本文将详细介绍 hashCode() 和 equals() 的本质区别和...

    重写hashCode()和equals()方法详细介绍

    在Java编程中,`equals()` 和 `hashCode()` 方法是Object类中的两个重要方法,它们在处理对象相等性以及在哈希表(如HashSet、HashMap)中起到关键作用。当自定义类时,有时需要根据业务逻辑重写这两个方法以满足...

    Java_重写equals()和hashCode()

    在Java编程语言中,`equals()` 和 `hashCode()` 方法是对象的基本组成部分,它们在很多场景下都发挥着至关重要的作用。这两个方法与对象的相等性比较和哈希表(如HashMap、HashSet)的运作紧密相关。这篇博客将深入...

    hashcode、equals、==总结1简单总结.txt

    hashcode、equals、==总结1简单总结.txt,他们之间的区别

    详解hashCode()和equals()的本质区别和联系

    详解hashCode()和equals()的本质区别和联系 在 Java 中,hashCode() 和 equals() 是两个非常重要的方法,它们都是从 Object 类继承来的。了解这两个方法的本质区别和联系对于 Java 开发者来说至关重要。 equals() ...

    重写equals和hashcode方法_equals_重写equals和hashcode方法_

    在Java编程语言中,`equals()` 和 `hashCode()` 方法是Object类中的两个核心方法,所有类都默认继承自Object类。这两个方法在处理对象比较和集合操作时起着至关重要的作用。当我们创建自定义类并需要对对象进行精确...

    hashcode()和equals()

    在Java编程语言中,`hashCode()` 和 `equals()` 方法是两个非常重要的概念,尤其是在处理对象比较和哈希表(如 `HashMap` 或 `HashSet`)时。这两个方法来源于 `Object` 类,是所有Java类的基类,因此,每个自定义类...

    java中hashcode()和equals()方法详解

    在Java编程语言中,`hashCode()`和`equals()`方法是非常重要的概念,它们不仅对于深入理解Java内存管理至关重要,也是实现自定义类的关键部分之一。本文将详细介绍这两个方法的工作原理、使用场景以及它们之间的关系...

    java中Hashcode的作用.docx

    Hashcode和equals方法是紧密相连的两个概念。在Java中,如果两个对象的equals方法返回true,那么这两个对象的Hashcode一定相同。反之,如果两个对象的Hashcode相同,那么这两个对象不一定是相等的。 Hashcode只是一...

    Java理论与实践:hashCode()和equals()方法

    本文还介绍了定义对象的相等性、实施equals()和hashCode()的需求、编写自己的equals()和hashCode()方法。通过统一定义equals()和hashCode(),可以提升类作为基于散列的集合中的关键字的使用性。

    Java重写equals同时需要重写hashCode的代码说明

    Java重写equals同时需要重写hashCode的代码说明,以及如何重写hashCode方法,此代码演示按照effective java书籍说明的重写思路。代码中演示了使用集合存储对象,并且对象作为key,需重写equals和hashCode.

    java面试题集锦.pdf

    本文将对 Java 面试题集锦中的知识点进行详细的解释和总结,涵盖了 Java 基础、Java 工具包、对象比较、hashCode 和 equals 等方面的知识点。 一、Java 基础 1. JDK 和 JRE 的区别 JDK(Java Development Kit)是...

    java中hashCode、equals的使用方法教程

    在Java编程语言中,`hashCode()` 和 `equals()` 方法对于对象的比较和处理至关重要,尤其在集合类(如Set和Map)中。这两个方法都源自`java.lang.Object`类,因此所有的Java类都默认继承了它们。理解并正确地重写这...

    java HashMap原理分析

    HashMap是一种高效的数据结构,它可以快速根据Key找到元素,但是需要正确地实现hashCode和equals方法,以避免哈希碰撞问题和equals方法的调用问题。 知识点: 1. 哈希函数的原理和应用 2. HashMap的存储原理和查询...

Global site tag (gtag.js) - Google Analytics