`
hz_chenwenbiao
  • 浏览: 1010546 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

深入理解HashCode

阅读更多

      在面试的时候被问到hashCode的作用,那时我没答好,所以现在在网上找了一些例子和讲解,现在总结一下:

 

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

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

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

package com.tools;

import java.util.ArrayList;

/**
* 此方法的作用是证明 java.lang.Object的hashcode 不是代表 对象所在内存地址。
* 我产生了10000个对象,这10000个对象在内存中是不同的地址,但是实际上这10000个对象
* 的hashcode的是完全可能相同的
*/
public class HashCodeMeaning {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        int numberExist=0;
        
        System.out.println("______________证明hashcode的值不是内存地址________________");
        //证明hashcode的值不是内存地址
        for (int i = 0; i < 10000; i++) {
            Object obj=new Object();//obj是内存地址
            if (list.contains(obj.hashCode())) {//获得对象的hashCode
                System.out.println(obj.hashCode() +" exists in the list. "+ i);
                numberExist++;
            }
            else {
                list.add(obj.hashCode());
            }
        }
        
        System.out.println("repetition number:"+numberExist);//和重复的hashCode,说明它不是内存地址
        System.out.println("list size:"+list.size());
        
        
        System.out.println("____________证明内存地址是不同的_______________");
        //证明内存地址是不同的。
        numberExist=0;
        list.clear();
        for (int i = 0; i < 10000; i++) {
            Object obj=new Object();//获得的是对象的内存地址
            if (list.contains(obj)) {//直接加入内存地址
                System.out.println(obj +" exists in the list. "+ i);
                numberExist++;
            }
            else {
                list.add(obj);
            }
        }
        
        //内存地址没重复
        System.out.println("repetition number:"+numberExist);
        System.out.println("list size:"+list.size());
    }
}

 


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 文件,找到:

intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
  if (UseBiasedLocking) {
    // NOTE: many places throughout the JVM do not expect a safepoint
    // to be taken here, in particular most operations on perm gen
    // objects. However, we only ever bias Java instances and all of
    // the call sites of identity_hash that might revoke biases have
    // been checked to make sure they can handle a safepoint. The
    // added check of the bias pattern is to avoid useless calls to
    // thread-local storage.
    if (obj->mark()->has_bias_pattern()) {
      // Box and unbox the raw reference just in case we cause a STW safepoint.
      Handle hobj (Self, obj) ;         
      // Relaxing assertion for bug 6320749.
      assert (Universe::verify_in_progress() ||
          !SafepointSynchronize::is_at_safepoint(),
         "biases should not be seen by VM thread here");
      BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
      obj = hobj() ; 
      assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
    }
  }

  // hashCode() is a heap mutator ...
  // Relaxing assertion for bug 6320749.
  assert (Universe::verify_in_progress() ||
      !SafepointSynchronize::is_at_safepoint(), "invariant") ; 
  assert (Universe::verify_in_progress() ||
      Self->is_Java_thread() , "invariant") ; 
  assert (Universe::verify_in_progress() ||
     ((JavaThread *)Self)->thread_state() != _thread_blocked, "invariant") ;

  ObjectMonitor* monitor = NULL;
  markOop temp, test;
  intptr_t hash;
  markOop mark = ReadStableMark (obj);

  // object should remain ineligible for biased locking 
  assert (!mark->has_bias_pattern(), "invariant") ; 
 
  if (mark->is_neutral()) {
    hash = mark->hash();              // this is a normal header
    if (hash) {                       // if it has hash, just return it
      return hash;
    }
    hash = get_next_hash(Self, obj);  // allocate a new hash code
    temp = mark->copy_set_hash(hash); // merge the hash code into header
    // use (machine word version) atomic operation to install the hash
    test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);
    if (test == mark) {
      return hash;
    }
    // If atomic operation failed, we must inflate the header
    // into heavy weight monitor. We could add more code here
    // for fast path, but it does not worth the complexity.
  } else if (mark->has_monitor()) {
    monitor = mark->monitor();
    temp = monitor->header();
    assert (temp->is_neutral(), "invariant") ; 
    hash = temp->hash();
    if (hash) {
      return hash;
    }
    // Skip to the following code to reduce code size
  } else if (Self->is_lock_owned((address)mark->locker())) {
    temp = mark->displaced_mark_helper(); // this is a lightweight monitor owned
    assert (temp->is_neutral(), "invariant") ; 
    hash = temp->hash();              // by current thread, check if the displaced
    if (hash) {                       // header contains hash code
      return hash;
    }
    // WARNING:
    //   The displaced header is strictly immutable.
    // It can NOT be changed in ANY cases. So we have 
    // to inflate the header into heavyweight monitor
    // even the current thread owns the lock. The reason
    // is the BasicLock (stack slot) will be asynchronously 
    // read by other threads during the inflate() function.
    // Any change to stack may not propagate to other threads
    // correctly.
  }

  // Inflate the monitor to set hash code
  monitor = ObjectSynchronizer::inflate(Self, obj);
  // Load displaced header and check it has hash code
  mark = monitor->header();
  assert (mark->is_neutral(), "invariant") ; 
  hash = mark->hash();
  if (hash == 0) {
    hash = get_next_hash(Self, obj);
    temp = mark->copy_set_hash(hash); // merge hash code into header
    assert (temp->is_neutral(), "invariant") ; 
    test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);
    if (test != mark) {
      // The only update to the header in the monitor (outside GC)
      // is install the hash code. If someone add new usage of
      // displaced header, please update this code
      hash = test->hash();
      assert (test->is_neutral(), "invariant") ; 
      assert (hash != 0, "Trivial unexpected object/monitor header usage.");
    }
  }
  // We finally get the hash
  return hash;
}

 这个函数基本上就在这了,原始的 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做法的目的。

 

分享到:
评论

相关推荐

    深入 HashCode 方法~

    ### 深入理解 HashCode 方法 #### 一、HashCode 的基本概念与作用 在 Java 编程语言中,`HashCode` 是一个非常重要且基础的概念。简单来说,`HashCode` 是一个整数值,用于快速定位对象的位置。在 Java 中,每一个...

    深入HashCode方法

    《深入理解HashCode方法》 HashCode方法在Java编程中扮演着至关重要的角色,尤其是在涉及对象存储和查找效率的数据结构,如HashMap和Hashtable中。一个对象的HashCode是一个简单的哈希算法实现,尽管它相对复杂的...

    深入理解Java中HashCode方法

    深入理解Java中HashCode方法 Java中的hashCode方法是每个类都需要实现的重要方法之一,它的主要作用是将对象的数据转换为一个32位的整数,用于标识对象的唯一性。在Java的所有类中,Object类是最顶层的父类,它定义...

    深入HashCode

    《深入HashCode》 在计算机科学领域,特别是在Java和许多其他面向...理解和正确实现`hashCode()`是每个程序员必备的技能之一。通过合理设计和优化`hashCode()`方法,我们可以提高程序的性能并确保数据结构的正确性。

    hashCode的作用

    为了更好地理解`hashCode`的作用及其在实际开发中的重要性,我们可以从以下几个方面进行深入探讨: #### 1. 基本概念 `hashCode`方法是`java.lang.Object`类中的一个方法,所有Java类都继承自`Object`类,因此每个...

    Java 中HashCode作用_动力节点Java学院整理

    本文将详细介绍 HashCode 的作用和重要性,为读者提供一个深入了解 HashCode 的机会。 HashCode 的作用 在 Java 中,HashSet、HashMap 等集合类的实现中,都是通过 HashCode 来实现对象的唯一性和快速查找的。那么...

    利用反射绕过编译器和hashcode高级应用

    本主题将深入探讨如何利用反射技术绕过编译器的一些限制,并介绍hashcode在高级应用中的用法。 首先,让我们理解反射的基本概念。在Java中,反射提供了一种方式,使我们能够在运行时动态地获取类的信息(如类名、...

    深入理解equals和hashCode方法

    深入理解equals和hashCode方法 equals和hashCode方法是Java中Object类提供的两个重要方法,对以后的学习有很大的帮助。本文将深入剖析这两个方法,帮助读者更好地理解和使用它们。 equals方法 equals方法是用于...

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

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

    Java_重写equals()和hashCode()

    总之,理解并正确重写 `equals()` 和 `hashCode()` 方法对于编写高质量的Java代码至关重要,这直接影响到对象比较的逻辑以及使用哈希表的数据结构的效率。通过遵循上述原则和最佳实践,我们可以确保对象的比较行为...

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

    本篇将深入探讨ArrayList与HashSet的区别,并分析Hashcode在其中的作用。 ArrayList是基于动态数组实现的,它提供了按索引访问元素的能力,就像在数组中一样。由于内部维护了一个数组,ArrayList保证了元素的顺序性...

    OCJP认证-3期(PX017) PX122010101018_OCJP考试大纲 -.doc

    深入理解hashCode()和equals()方法的重要性,以及Comparable接口和Comparator接口在排序中的作用。 输入输出流是Java处理数据传输的关键,考试会涉及文件类,如File,以及各种输入/输出流,包括FileInputStream、...

    Java equals 方法与hashcode 方法的深入解析.rar

    总的来说,理解并正确使用`equals()`和`hashCode()`方法对于编写高质量的Java代码至关重要,尤其是在处理集合框架中的对象时。这两个方法的合理实现能确保对象比较的正确性和哈希表操作的效率。希望这个深入解析能...

    hashcode与eqault比较

    综上所述,正确理解和使用`equals`和`hashCode`方法对于编写高质量的Java程序至关重要。通过对这两个方法的合理覆盖,可以有效地提高数据结构(如`HashSet`和`HashMap`)的操作效率,同时也能更好地支持对象之间的...

    hashCode内存溢出和内存泄漏的问题解决.docx

    本文将深入探讨内存泄漏及其可能导致的内存溢出问题,以及如何识别和解决这些问题。 首先,我们需要明确什么是内存泄漏。内存泄漏是指程序中已经分配的内存块在不再使用后,由于编程错误或设计缺陷,没有被正确地...

    hashcode和equals的分析

    在深入探讨`hashCode`方法之前,我们需要了解Java集合框架的基本概念。Java集合框架主要包括两大类集合:`List`和`Set`。 - **List**:这是一个有序集合,允许元素重复。 - **Set**:这是一个不允许元素重复的无序...

    java中hashcode和equals的详解.pdf

    通过对 hashCode 和 equals 方法的深入分析,我们可以更好地理解 Java 集合的实现原理和哈希表的工作机制。 一、hashCode 方法简介 hashCode 方法是 Java 中 Object 类的一个方法,用于返回对象的哈希码值。这个...

    如何生成一个合适的hashcode方法Java开发Java

    本篇文章将深入探讨如何在Java中生成一个合适的`hashCode()`方法,以及在开发过程中应该注意的关键点。 首先,理解`hashCode()`方法的作用至关重要。当我们将一个对象放入哈希表时,`hashCode()`方法被调用以生成一...

    java 中HashCode重复的可能性

    下面我们将深入探讨 Java 中 HashCode 重复的可能性。 首先,让我们了解什么是 Hash Code。Hash Code 是一种对象标识符,它用于标识对象的唯一性。在 Java 中,每个对象都有一个 Hash Code,它可以通过 hashCode() ...

Global site tag (gtag.js) - Google Analytics