`

深入理解HashCode

阅读更多

哈希码产生的依据: 
      哈希码并不是完全唯一的,它是一种算法,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同。也有相同的情况,看程序员如何写哈希码的算法。 

下面给出几个常用的哈希码的算法:

1:Object类的hashCode.返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以hashcode可以做到尽可能的不一样,但我们要清楚一点,既然是用到hash技术,就是要解决冲突的,所以hashcode是会出现相同的时候,我们可以将hashCode相同的看成放入同一个桶中。

Java代码  收藏代码
  1. package com.tools;  
  2.   
  3. import java.util.ArrayList;  
  4.   
  5. /** 
  6. * 此方法的作用是证明 java.lang.Object的hashcode 不是代表 对象所在内存地址。 
  7. * 我产生了10000个对象,这10000个对象在内存中是不同的地址,但是实际上这10000个对象 
  8. * 的hashcode的是完全可能相同的 
  9. */  
  10. public class HashCodeMeaning {  
  11.     public static void main(String[] args) {  
  12.         ArrayList list = new ArrayList();  
  13.         int numberExist=0;  
  14.           
  15.         System.out.println("______________证明hashcode的值不是内存地址________________");  
  16.         //证明hashcode的值不是内存地址  
  17.         for (int i = 0; i < 10000; i++) {  
  18.             Object obj=new Object();//obj是内存地址  
  19.             if (list.contains(obj.hashCode())) {//获得对象的hashCode  
  20.                 System.out.println(obj.hashCode() +" exists in the list. "+ i);  
  21.                 numberExist++;  
  22.             }  
  23.             else {  
  24.                 list.add(obj.hashCode());  
  25.             }  
  26.         }  
  27.           
  28.         System.out.println("repetition number:"+numberExist);//和重复的hashCode,说明它不是内存地址  
  29.         System.out.println("list size:"+list.size());  
  30.           
  31.           
  32.         System.out.println("____________证明内存地址是不同的_______________");  
  33.         //证明内存地址是不同的。  
  34.         numberExist=0;  
  35.         list.clear();  
  36.         for (int i = 0; i < 10000; i++) {  
  37.             Object obj=new Object();//获得的是对象的内存地址  
  38.             if (list.contains(obj)) {//直接加入内存地址  
  39.                 System.out.println(obj +" exists in the list. "+ i);  
  40.                 numberExist++;  
  41.             }  
  42.             else {  
  43.                 list.add(obj);  
  44.             }  
  45.         }  
  46.           
  47.         //内存地址没重复  
  48.         System.out.println("repetition number:"+numberExist);  
  49.         System.out.println("list size:"+list.size());  
  50.     }  
  51. }  

 

 


2:String类的hashCode.根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串内容相同,返回的哈希码也相同,这个可以简单实现如将串的各个字母相与的结果作为hashcode,而sun实现比这个复杂多。

3:Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integer i1=new Integer(100),i1.hashCode的值就是100 。由此可见,2个一样大小的Integer对象,返回的哈希码也一样。

  public  native  int  hashCode(); 
      
        由于是native方法,跟OS的处理方式相关,源代码里仅仅有一个声明罢了。我们有兴趣的话完全可以去深究它的hashCode到底是由OS怎么样产生的呢?但笔者建议最重要的还是先记住使用它的几条原则吧!首先如果equals()方法相同的对象具有相通的hashCode,但equals  ()对象不相通的时候并不保证hashCode()方法返回不同的整数。而且下一次运行同一个程序,同一个对象未必还是当初的那个hashCode()  哦。

 

到 OpenJDK 下载 OpenJDK 的源代码,解压后找到 hotspot/src/share/vm/runtime 目录,里面有个 synchronizer.cpp 文件,找到:

 

Cpp代码  收藏代码
  1. intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {  
  2.   if (UseBiasedLocking) {  
  3.     // NOTE: many places throughout the JVM do not expect a safepoint  
  4.     // to be taken here, in particular most operations on perm gen  
  5.     // objects. However, we only ever bias Java instances and all of  
  6.     // the call sites of identity_hash that might revoke biases have  
  7.     // been checked to make sure they can handle a safepoint. The  
  8.     // added check of the bias pattern is to avoid useless calls to  
  9.     // thread-local storage.  
  10.     if (obj->mark()->has_bias_pattern()) {  
  11.       // Box and unbox the raw reference just in case we cause a STW safepoint.  
  12.       Handle hobj (Self, obj) ;           
  13.       // Relaxing assertion for bug 6320749.  
  14.       assert (Universe::verify_in_progress() ||  
  15.           !SafepointSynchronize::is_at_safepoint(),  
  16.          "biases should not be seen by VM thread here");  
  17.       BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());  
  18.       obj = hobj() ;   
  19.       assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");  
  20.     }  
  21.   }  
  22.   
  23.   // hashCode() is a heap mutator ...  
  24.   // Relaxing assertion for bug 6320749.  
  25.   assert (Universe::verify_in_progress() ||  
  26.       !SafepointSynchronize::is_at_safepoint(), "invariant") ;   
  27.   assert (Universe::verify_in_progress() ||  
  28.       Self->is_Java_thread() , "invariant") ;   
  29.   assert (Universe::verify_in_progress() ||  
  30.      ((JavaThread *)Self)->thread_state() != _thread_blocked, "invariant") ;  
  31.   
  32.   ObjectMonitor* monitor = NULL;  
  33.   markOop temp, test;  
  34.   intptr_t hash;  
  35.   markOop mark = ReadStableMark (obj);  
  36.   
  37.   // object should remain ineligible for biased locking   
  38.   assert (!mark->has_bias_pattern(), "invariant") ;   
  39.    
  40.   if (mark->is_neutral()) {  
  41.     hash = mark->hash();              // this is a normal header  
  42.     if (hash) {                       // if it has hash, just return it  
  43.       return hash;  
  44.     }  
  45.     hash = get_next_hash(Self, obj);  // allocate a new hash code  
  46.     temp = mark->copy_set_hash(hash); // merge the hash code into header  
  47.     // use (machine word version) atomic operation to install the hash  
  48.     test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);  
  49.     if (test == mark) {  
  50.       return hash;  
  51.     }  
  52.     // If atomic operation failed, we must inflate the header  
  53.     // into heavy weight monitor. We could add more code here  
  54.     // for fast path, but it does not worth the complexity.  
  55.   } else if (mark->has_monitor()) {  
  56.     monitor = mark->monitor();  
  57.     temp = monitor->header();  
  58.     assert (temp->is_neutral(), "invariant") ;   
  59.     hash = temp->hash();  
  60.     if (hash) {  
  61.       return hash;  
  62.     }  
  63.     // Skip to the following code to reduce code size  
  64.   } else if (Self->is_lock_owned((address)mark->locker())) {  
  65.     temp = mark->displaced_mark_helper(); // this is a lightweight monitor owned  
  66.     assert (temp->is_neutral(), "invariant") ;   
  67.     hash = temp->hash();              // by current thread, check if the displaced  
  68.     if (hash) {                       // header contains hash code  
  69.       return hash;  
  70.     }  
  71.     // WARNING:  
  72.     //   The displaced header is strictly immutable.  
  73.     // It can NOT be changed in ANY cases. So we have   
  74.     // to inflate the header into heavyweight monitor  
  75.     // even the current thread owns the lock. The reason  
  76.     // is the BasicLock (stack slot) will be asynchronously   
  77.     // read by other threads during the inflate() function.  
  78.     // Any change to stack may not propagate to other threads  
  79.     // correctly.  
  80.   }  
  81.   
  82.   // Inflate the monitor to set hash code  
  83.   monitor = ObjectSynchronizer::inflate(Self, obj);  
  84.   // Load displaced header and check it has hash code  
  85.   mark = monitor->header();  
  86.   assert (mark->is_neutral(), "invariant") ;   
  87.   hash = mark->hash();  
  88.   if (hash == 0) {  
  89.     hash = get_next_hash(Self, obj);  
  90.     temp = mark->copy_set_hash(hash); // merge hash code into header  
  91.     assert (temp->is_neutral(), "invariant") ;   
  92.     test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);  
  93.     if (test != mark) {  
  94.       // The only update to the header in the monitor (outside GC)  
  95.       // is install the hash code. If someone add new usage of  
  96.       // displaced header, please update this code  
  97.       hash = test->hash();  
  98.       assert (test->is_neutral(), "invariant") ;   
  99.       assert (hash != 0, "Trivial unexpected object/monitor header usage.");  
  100.     }  
  101.   }  
  102.   // We finally get the hash  
  103.   return hash;  
  104. }  

 这个函数基本上就在这了,原始的 hashCode 在 jdk/src/share/native/java/lang/Object.c 这个文件中,调来调去就调到上面那个函数去了。

OpenJDK 6 的下载页面:http://download.java.net/openjdk/jdk6/ 上面有个 46.9MB 的 tar.gz 文件,下载回来后是整个 JDK(JVM、JDK 工具和 J2SE 类库和底层类库)的源代码,解压后有 254MB,大约有 28750 个文件。

 

 

 

 

 

 

 

2.  关于重载hashCode()与Collection框架的关系 
笔者曾经听一位搞Java培训多年的前辈说在他看来hashCode方法没有任何意义,仅仅是为了配合证明具有同样的hashCode会导致equals  方法相等而存在的。连有的前辈都犯这样的错误,其实说明它还是满容易被忽略的。那么hashCode()方法到底做什么用? 
      
        学过数据结构的课程大家都会知道有一种结构叫hash  table,目的是通过给每个对象分配一个唯一的索引来提高查询的效率。那么Java也不会肆意扭曲改变这个概念,所以hashCode唯一的作用就是为支持数据结构中的哈希表结构而存在的,换句话说,也就是只有用到集合框架的  Hashtable、HashMap、HashSet的时候,才需要重载hashCode()方法, 
这样才能使得我们能人为的去控制在哈希结构中索引是否相等。笔者举一个例子: 
        曾经为了写一个求解类程序,需要随机列出1,2,3,4组成的不同排列组合,所以笔者写了一个数组类用int[]来存组合结果,然后把随机产生的组合加入一个HashSet中,就是想利用HashSet不包括重复元素的特点。可是HashSet怎么判断是不是重复的元素呢?当然是通过  hashCode()返回的结果是否相等来判断啦,可做一下这个实验: 
        int[]  A  =  {1,2,3,4}; 
        int[]  B  =  {1,2,3,4}; 
        System.out.println(A.hashCode()); 
        System.out.println(B.hashCode()); 

        这明明是同一种组合,却是不同的hashCode,加入Set的时候会被当成不同的对象。这个时候我们就需要自己来重写hashCode()方法了,如何写呢?其实也是基于原始的hashCode(),毕竟那是操作系统的实现,  找到相通对象唯一的标识,实现方式很多,笔者的实现方式是: 
        首先重写了toString()方法: 
        return    A[0]“+”  A[1]“+”  A[2]“+”  A[3];  //显示上比较直观 
        然后利用toString()来计算hashCode(): 
        return    this.toString().hashCode(); 
        这样上述A和B返回的就都是”1234”,在测试toString().hashCode(),由于String在内存中的副本是一样的,”1234”.hashCode()返回的一定是相同的结果。 

        说到这,相信大家能理解得比我更好,今后千万不要再误解hashCode()方法的作用。 

 

 


        其余的方法呢?nofigy()、notifyAll()、clone()、wait()都是native方法的,说明依赖于操作系统的实现。最后一个有趣的方法是finalize(),类似C++的析构函数,签名是protected,证明只有继承扩展了才能使用,方法体是空的,默示什么也不做。它的作用据笔者的了解仅仅是通知JVM此对象不再使用,随时可以被销毁,而实际的销毁权还是在于虚拟机手上。那么它真的什么也不做麽?未必,实际上如果是线程对象它会导致在一定范围内该线程的优先级别提高,导致更快的被销毁来节约内存提高性能。其实从常理来说,我们也可以大概这样猜测出jvm做法的目的。

分享到:
评论

相关推荐

    深入理解Java中HashCode方法

    主要介绍了深入理解Java中HashCode方法,具有一定借鉴价值,需要的朋友可以参考下

    深入理解equals和hashCode方法

    在Java中,equals和hashCode方法是Object中提供的两个方法,这两个方法对以后的学习有很大的帮助,本文就深度来去讲解这两个方法。下面小编带大家来一起学习吧

    关于Java中HashCode方法的深入理解

    主要给大家介绍了关于Java中HashCode方法的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

    Java hashCode() 方法详细解读

    Java.lang.Object 有一个hashCode()和一个equals()方法,这两个方法在软件设计中扮演着举足轻重的角色,本文对hashCode()方法深入理解,希望能帮助大家

    Java的Object类讲解案例代码 equals()、hashCode()、finalize()、clone()、wait()

    经验丰富的Java开发者:即使你已经有一定的Java开发经验,仍然值得深入了解Object类的使用。该案例代码将提供一些实际应用场景,帮助你更加灵活地使用Object类的相关方法,从而优化你的项目。 通过这个源码资源,你...

    Java实例高难度面试题及解析 - 展现你的编程实力!

    通过研究和解答这些高难度问题,您将提升自己的编程水平,展现出对Java实例概念和相关技术的深入理解。无论您是准备面试还是想扩展自己的Java知识,本文都将为您提供宝贵的学习资源和技巧。让我们一起进入Java实例的...

    Java中HashMap和TreeMap的区别深入理解

     HashMap通过hashcode对其内容进行快速查找,而 TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。  HashMap 非线程安全 ...

    Java Object 类高难度进阶版面试题集锦解析Java Object类高难度面试题及答案解析

    提供了20道高难度的Java Object类面试题及详细答案解析,涵盖了equals()、hashCode()、toString()、clone()、finalize()等方法的重写和应用,以及对象...适合准备Java面试的开发者深入理解和掌握Object类的关键知识点。

    程序员需要经常刷题吗-simple-java-zh-CN:SimpleJava是Java常见问题的集合。中文翻译

    Simple Java 是 Java 常见问题的集合。中文翻译 ##1。 字符串和数组字符串和数组 字符串是通过引用传递的吗?...深入理解Arrays.sort(T[], Comparator &lt; ? super T &gt; c) 常见排序,Collections、Arrays、Tre

    疯狂JAVA讲义

    学生提问:hashCode方法对于HashSet的作用是什么? 249 7.3.2 TreeSet类 252 7.3.3 EnumSet类 259 7.4 List接口 261 7.4.1 List接口和ListIterator接口 261 7.4.2 ArrayList和Vector实现类 264 7.4.3 固定长度...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    7.11 小结:多方位理解Java方法 191 7.12 习题 192 第8章 Java中的包(Package)命名习惯和注释 193 教学视频:43分钟 8.1 Java中的包(Package) 193 8.1.1 Java中的包 193 8.1.2 在Eclipse中使用包 194 ...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    7.11 小结:多方位理解Java方法 191 7.12 习题 192 第8章 Java中的包(Package)命名习惯和注释 193 教学视频:43分钟 8.1 Java中的包(Package) 193 8.1.1 Java中的包 193 8.1.2 在Eclipse中使用包 194 ...

    JAVA基础课程讲义

    第一个简单的IO流程序及深入(将文件中的数据读入) 146 Java中流的概念细分 148 Java中IO流类的体系 149 四个IO基本抽象类 150 InputStream 150 OutputStream 150 常用InputStream和OutputStream子类用法 150 ...

Global site tag (gtag.js) - Google Analytics