• K:java中的hashCode和equals方法


      hashCode和equals方法是Object类的相关方法,而所有的类都是直接或间接的继承于Object类而存在的,为此,所有的类中都存在着hashCode和equals。通过翻看Object类的相关源码,发现其hashCode的实现方式如下:

    public native int hashCode();
    

      从中可以看出,hashCode的实现是一个本地方法,并且其返回了一个int型的值。很多人都认为在默认情况下,hashCode返回的就是对象的存储地址,事实上这样的看法是不全面的,确实有些JVM在实现时是直接返回对象的存储地址的,但是在大多数的时候,其只能说是与存储地址有一定的关联,下面的是HotSpot JVM中生成hash散列值的实现:

    @代码来源:http://www.cnblogs.com/dolphin0520/p/3681042.html
    
    static inline intptr_t get_next_hash(Thread * Self, oop obj) {
      intptr_t value = 0 ;
      if (hashCode == 0) {
         // This form uses an unguarded global Park-Miller RNG,
         // so it's possible for two threads to race and generate the same RNG.
         // On MP system we'll have lots of RW access to a global, so the
         // mechanism induces lots of coherency traffic.
         value = os::random() ;
      } else
      if (hashCode == 1) {
         // This variation has the property of being stable (idempotent)
         // between STW operations.  This can be useful in some of the 1-0
         // synchronization schemes.
         intptr_t addrBits = intptr_t(obj) >> 3 ;
         value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
      } else
      if (hashCode == 2) {
         value = 1 ;            // for sensitivity testing
      } else
      if (hashCode == 3) {
         value = ++GVars.hcSequence ;
      } else
      if (hashCode == 4) {
         value = intptr_t(obj) ;
      } else {
         // Marsaglia's xor-shift scheme with thread-specific state
         // This is probably the best overall implementation -- we'll
         // likely make this the default in future releases.
         unsigned t = Self->_hashStateX ;
         t ^= (t << 11) ;
         Self->_hashStateX = Self->_hashStateY ;
         Self->_hashStateY = Self->_hashStateZ ;
         Self->_hashStateZ = Self->_hashStateW ;
         unsigned v = Self->_hashStateW ;
         v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
         Self->_hashStateW = v ;
         value = v ;
      }
     
      value &= markOopDesc::hash_mask;
      if (value == 0) value = 0xBAD ;
      assert (value != markOopDesc::no_hash, "invariant") ;
      TEVENT (hashCode: GENERATE) ;
      return value;
    }
    
    
    ps:该实现位于hotspot/src/share/vm/runtime/synchronizer.cpp文件下。
    

      hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。Hash相关的数据结构是根据对象的相关信息(可以称为键),通过一定的运算规则将其散列映射到一个数值上的,为此,Hash相关的数据结构具有查找和插入的速度都较快的优点。Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值也称为哈希值。而需要注意的一点是在一般情况下即使两个对象的hash值相同也不能判定这两个对象的相关信息是相同的,因为hash函数一般而言都不会是一个双射函数,为此有碰撞产生的情况存在(即两个不同的对象会得到相同的hash值)。为此,在比较两个对象是否相同的时候,便需要有equals方法作为辅助了。

      至于equals方法,相信学过java基础的人都会知道,在String等类中其用于判断两个对象的值是否相等,至于在Object类中,其实现如下:

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

      从中可以发现,对于Object类来说,其equals方法判断的只是两个对象是不是同一个对象而已,当为同一个对象的时候返回true,否则返回false。而这并不能够实现判断两个对象的值是否相同的功能,通过查看String类中的equals方法的相关源码:

    public boolean equals(Object anObject) {
    	if (this == anObject) {
    	    return true;
    	}
    	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;
        }
    

    我们可以发现,其重写了Object类的equals方法,使得其可以对两个对象的值是否一致进行判断。

      至此,我们对于hashCode和equals方法的作用有了一定的了解,hashCode方法的作用是根据对象的相关信息(对象的字段值,对象的存储地址)通过一定的运算规则获得一个int型值,该int值在一定程度上反应了对象的有关信息。而equals方法的作用是用于判断两个对象的相关的指定信息是否是一样的。在Hash相关的数据结构中通过结合使用hashCode方法以及equals方法来提高插入和查找效率。

    例如,java.util.HashMap的中put方法的具体实现:

    public V put(K key, V value) {
            if (key == null)
                return putForNullKey(value);
            int hash = hash(key.hashCode());
            int i = indexFor(hash, table.length);
            for (Entry<K,V> e = table[i]; e != null; e = e.next) {
                Object k;
                if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                    V oldValue = e.value;
                    e.value = value;
                    e.recordAccess(this);
                    return oldValue;
                }
            }
     
            modCount++;
            addEntry(hash, key, value, i);
            return null;
        }
    

      一般而言,对于使用Hash相关的数据结构,其会先通过调用对象的hashCode方法获取该对象的hash值,缩小了查找范围之后,再通过调用equals方法进行“精细”的查找,以缩短查找时间和范围,提高查找效率。

      通过以上的分析,我们需要注意到如下的几点:

    1. 不同的对象可能会生成相同的hash值。为此,不能通过hash值来判断两个对象是否相等,但是可以通过hash值来判断两个对象的不相等,即hash值相等的两个对象,其不一定是相等的,而hash值不相等的两个对象,其一定是不相等的。
    2. equals方法返回结果为ture的一定是相等的对象

    由以上的这两点,我们可以推出如下的四点内容:

    1. 如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等
    2. 如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同
    3. 如果两个对象的hashcode值不等,则equals方法得到的结果必定为false
    4. 如果两个对象的hashcode值相等,则equals方法得到的结果未知。

    为此,我们可以知道,在重写equals方法的时候,必须要重写了hashCode方法。

    在重写equals方法和hashCode方法的时候,需要注意以下几点:

    1. 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。
    2. 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。
    3. 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。

    ------------------------------------------->摘自《Effective Java》

    重写equals和hashCode方法的一个例子:

    public class HashCodeTest
    {
    	private String name="小小";
    	private int age=12;
    	@Override
    	public int hashCode()
    	{
    		return age*37+name.hashCode();
    	}
    	@Override
    	public boolean equals(Object other)
    	{
    		if(other==this)
    			return true;
    		if(other==null)
    			return false;
    		if(other instanceof HashCodeTest)
    		{
    			HashCodeTest t=(HashCodeTest) other;
    			return this.age==t.age&&this.name.equals(t.name);
    		}
    		return false;
    	}
    }
    
    

    注意的一点是:
    在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段

    回到目录|·(工)·)

  • 相关阅读:
    51 Nod 1086 多重背包问题(单调队列优化)
    51 Nod 1086 多重背包问题(二进制优化)
    51 Nod 1085 01背包问题
    poj 2559 Largest Rectangle(单调栈)
    51 Nod 1089 最长回文子串(Manacher算法)
    51 Nod N的阶乘的长度 (斯特林近似)
    51 Nod 1134 最长递增子序列(经典问题回顾)
    51 Nod 1020 逆序排列
    PCA-主成分分析(Principal components analysis)
    Python中cPickle
  • 原文地址:https://www.cnblogs.com/MyStringIsNotNull/p/8023853.html
Copyright © 2020-2023  润新知