zoukankan      html  css  js  c++  java
  • 浅析hashcode和equals

     前言:   hashCode是jdk根据对象的地址  或者  字符串或者数字  算出来的int类型的数值。

    1.  jdk源代码 ——不同类中的hashcode方法

      1:Object类的hashCode().返回对象的内存地址,由于每个对象的内存地址都不一样,所以哈希码也不一样。

        

     public native int hashCode();
    这就是Object.java中的hashcode源代码
    分析:1.使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。
        这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的,这也是java的底层机制。
        实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。 
       2.native的意思就是通知操作系统,这个函数你必须给我实现,因为我要使用。所以native关键字的函数都是操作系统实现的, java只能调用。

      2:String类的hashCode.根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串所在的堆空间相同,返回的哈希码也相同。

    String.java 类中的hashcode源代码
     public int hashCode() {
            int h = hash;
            if (h == 0 && value.length > 0) {
                char val[] = value;
    
                for (int i = 0; i < value.length; i++) {
                    h = 31 * h + val[i];
                }
                hash = h;
            }
            return h;
        }
    分析:

    String类中的hashCode计算方法还是比较简单的,就是以31为权,每一位为字符的ASCII值进行运算,用自然溢出来等效取模。

    哈希计算公式可以计为s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

    关于为什么取31为权?主要是因为31是一个奇质数,所以31*i=32*i-i=(i<<5)-i,这种位移与减法结合的计算相比一般的运算快很多。

     String s1 = new String("2");
            String s2 = "2";
            String s3 = "A";
            String s4 = "a";
            System.out.println(s1.hashCode());   //50
            System.out.println(s2.hashCode());   //50
            System.out.println(s3.hashCode()+" "+s4.hashCode());   //65   97

      3:Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值。

     @Override
        public int hashCode() {
            return Integer.hashCode(value);
        }
    Integer i2 = new Integer(2);
    Integer i3 = new Integer(2);
    System.out.println(i2.hashCode()+" "+i3.hashCode());  // 2   2

    2.  jdk源代码 ——不同类中的equals方法

    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”
    

      

      1 .  Object.java 类中的equals方法 ,  默认比较的是对象的地址,因为只有是相同的地址才会相等.

    1 //lang包--   Object.java  中的equals();
    2     public boolean equals(Object obj) {
    3         return (this == obj);
    4     }

      2 .  String.java类中的equals方法 , 比较的是对象的值。   

    判断条件分为四步:

              1.  若当前对象和比较的对象是同一个对象,即return true。也就是Object中的equals方法。

         2.  若当前传入的对象是String类型,则比较两个字符串的长度,即value.length的长度。 若长度不相同,则return false

              3.  若长度相同,则按照数组value中的每一位进行比较,不同,则返回false。若每一位都相同,则返回true。

              4.  若当前传入的对象不是String类型,则直接返回false

    public boolean equals(Object anObject)
    {
           //如果是同一个对象
            if (this == anObject)
            {
                return true;
            }
            //如果传递进来的参数是String类的实例
            if (anObject instanceof String)
            {
                String anotherString = (String)anObject;
                int n = count;//字符串长度
                if (n == anotherString.count) //如果长度相等就进行比较
                {
                    char v1[] = value;//取每一个位置的字符
                    char v2[] = anotherString.value;
                    int i = offset;
                    int j = anotherString.offset;
                    while (n-- != 0) //对于每一位置逐一比较
                    {
                        if (v1[i++] != v2[j++])
                            return false;
                    }
                    return true;
                }
            }
            return false;
    }

      3.   Integer类中的equals方法 ,  首先判断比较的对象是不是Integer类型的,若是,则转为int基本类型,再用 == 比较,比较的就是数值了

    public boolean equals(Object obj) {
            if (obj instanceof Integer) {
                return value == ((Integer)obj).intValue();
            }
            return false;
        }

    Integer类中的intValue方法

    public int intValue() {
       return value;
    }

     

    3. 先提出个问题: 当向 HashSet 集合 (无须,不重复) 中插入对象时,如何判别在集合中是否已经存在该对象了?

        分析: 1. 如果先用 equals方法来逐个进行比较,倘若集合中已经存在一万条数据或者更多的数据,效率必然是一个问题

          2. 此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不插入,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了。

    说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值

    下面这段代码是java.util.HashMap的中put方法的具体实现:

    public V put(K key, V value) {
            //当key为null,调用putForNullKey方法,保存null与table第一个位置中,这是HashMap允许为null的原因
            if (key == null)
                return putForNullKey(value);
            //计算key的hash值
            int hash = hash(key.hashCode());                  ------(1)
            //计算key hash 值在 table 数组中的位置
            int i = indexFor(hash, table.length);             ------(2)
            //从i出开始迭代 e,找到 key 保存的位置
            for (Entry<K, V> e = table[i]; e != null; e = e.next) {
                Object k;
                //判断该条链上是否有hash值相同的(key相同)
                //若存在相同,则直接覆盖value,返回旧value
                if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                    V oldValue = e.value;    //旧值 = 新值
                    e.value = value;
                    e.recordAccess(this);
                    return oldValue;     //返回旧值
                }
            }
            //修改次数增加1
            modCount++;
            //将key、value添加至i位置处
            addEntry(hash, key, value, i);
            return null;
        }

        put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。

    总结:

    HashSet判断两个对象是否相等 必须同时满足两个条件:

    1.    hashcode是否相等
    2.  equals是否相等

    4. 几种情况比较

       1.  只看java类库中的各个方法,没有自定义重写的情况下。

        两个对象==相等,则其hashcode一定相等,反之不一定成立。

        两个对象equals相等,则其hashcode一定相等,反之不一定成立。?【 因为Object的equals实现用的就是 对象的==相等来判断】

         String s1 = new String("2");
            String s2 = new String("2");
            String s3 = "4";
            String s4 = "4";
            
            System.out.println(s1.hashCode()==s2.hashCode());   //true
            System.out.println(s1.equals(s2));  //true      //Stirng类中重写了equals方法
            System.out.println(s1==s2);         //false    //两个对象的hashcode 相同,== 不一定相同。
            
            System.out.println(s3.hashCode()==s4.hashCode());   //true
            System.out.println(s3.equals(s4));  //true
            System.out.println(s3==s4);        //true

    5. 为什么重写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方法的。

    6.根据实际需求重写equals 方法和 hashcode 方法(常见的写法如下)

            
            @Override
            public boolean equals(Object obj){
                if(obj == null){
                    return false;
                }
                if(this == obj){
                    return false;
                }
                
                if(obj instanceof Stduent){
                    Stduent ss = (Stduent)obj;
                    if(this.id==ss.id&&this.name.equals(ss.name)){
                        return true;
                    }
                }
                return false;
            }
            
            @Override
            public int hashCode() {
                //return this.id+this.name.hashCode();//返回的是跟塑所有属性值 相关 的int值
                //上面这种写法产生的问题是不同的属性值相加之后  可能会存在相同的结果
                //解决办法: 将每个属性值用字符串进行拼接,不会去运算,也就不会产生上述问题
                String s =   ""  +  this.id+this.name.hashCode();
                return Integer.parseInt(s);
            }

     

  • 相关阅读:
    【酷熊科技】工作积累 ----------- 随机数 (带权重的随机数)
    【酷熊科技】工作积累 ----------- unity scrollview 点击后会有偏移问题(有图片)
    cocos2dx混合模式应用———制作新手引导高亮区域 (2.2.0)
    3.20 内存及效率的一些总结 3.21 设置竖屏 3.22 CCLOG与CCLog区别
    3.18 CCProgressTo 进度计时器
    3.16 draw 3.17 更新函数
    3.15 获取当前设备语言
    CCControlSwitch 、CCControlSlider、CCControlButton
    CCEditBox
    CCTextFieldTTF 与 5种常用CCMenuItem
  • 原文地址:https://www.cnblogs.com/gshao/p/10127667.html
Copyright © 2011-2022 走看看