Java中的hashCode()方法如何计算

64

hashCode()方法在Java中返回什么值?

我了解到它是对象的内存引用...new Integer(1)的哈希值是1;String("a")的哈希值是97。

我有些困惑:这是ASCII码还是其他类型的值?

11个回答

55
hashCode()返回的值并不保证是对象的内存地址。对于Object类中的实现我不确定,但请记住,大多数类会覆盖hashCode()方法以使得两个语义上相等(但不是同一个实例)的实例哈希到相同的值。如果这些类可能在另一个数据结构中使用,例如Set,那么hashCode必须与equals方法一致。

即使使用System.identityHashCode(),也不能保证为每个对象实例生成唯一的哈希码,例如在Sun的实现中。如果您想要基于底层指针生成哈希码,可以使用System.identityHashCode() - 这将委派给默认的hashCode()方法,无论它是否被覆盖。

尽管如此,System.identityHashCode()也可能为多个对象返回相同的哈希值。查看注释以了解原因,以下是一个示例程序,它会不断生成对象,直到找到两个具有相同System.identityHashCode()值的对象。当我运行它时,它很快就找到了两个匹配的System.identityHashCode()值,平均添加了约86,000个Long包装对象(以及用作键的Integer包装对象)到Map中。

public static void main(String[] args) {
    Map<Integer,Long> map = new HashMap<>();
    Random generator = new Random();
    Collection<Integer> counts = new LinkedList<>();

    Long object = generator.nextLong();
    // We use the identityHashCode as the key into the map
    // This makes it easier to check if any other objects
    // have the same key.
    int hash = System.identityHashCode(object);
    while (!map.containsKey(hash)) {
        map.put(hash, object);
        object = generator.nextLong();
        hash = System.identityHashCode(object);
    }
    System.out.println("Identical maps for size:  " + map.size());
    System.out.println("First object value: " + object);
    System.out.println("Second object value: " + map.get(hash));
    System.out.println("First object identityHash:  " + System.identityHashCode(object));
    System.out.println("Second object identityHash: " + System.identityHashCode(map.get(hash)));
}

输出示例:

Identical maps for size:  105822
First object value: 7446391633043190962
Second object value: -8143651927768852586
First object identityHash:  2134400190
Second object identityHash: 2134400190

3
这个被踩的原因有什么特别的吗?如果这里有错误,我会很乐意接受纠正,但是没有解释地进行负投票对讨论毫无帮助。 - danben
8
几年前,Ted Neward在他的博客 http://blogs.tedneward.com/2008/07/16/ObjecthashCode+Implementation.aspx 中解释了OpenJDK如何实现Object.hashCode()。OpenJDK从对象地址派生哈希码,但会缓存该值并返回给后续调用者,以防对象在内存中移动并改变其地址。经过简要审查最新的代码,我发现自Neward撰写文章以来,该实现似乎并没有改变。 - Derek Mahar
1
这是否意味着移动的对象哈希码可能会与另一个新对象的哈希码相同? - devoured elysium
11
可以:它可能会“撞上”另一个新对象的哈希码。哈希码不被视为唯一。 “哈希码”旨在“缩小”唯一性(对于哈希表),但您必须始终使用“equals”跟随它。 - ChrisCantrell
@ChrisCantrell。如果您想确保两个引用指向同一对象,可以使用Yes或==。 - Josiah Yoder
显示剩余6条评论

51

哈希码是表示调用它的对象状态的整数值。因此,Integer 设置为1将返回哈希码"1",因为Integer的哈希码和它的值是一样的。字符的哈希码等于其ASCII字符代码。如果编写自定义类型,则需要创建良好的hashCode实现,以最好地表示当前实例的状态。


25
如果你想知道它们是如何实现的,我建议你阅读源代码。如果你使用的是IDE,你可以在你感兴趣的方法上单击加号,查看该方法的实现方式。如果你不能这样做,你可以谷歌搜索源代码。

举个例子,Integer.hashCode()的实现如下:
   public int hashCode() {
       return value;
   }

和 String.hashCode()

   public int hashCode() {
       int h = hash;
       if (h == 0) {
           int off = offset;
           char val[] = value;
           int len = count;

           for (int i = 0; i < len; i++) {
               h = 31*h + val[off++];
           }
           hash = h;
       }
       return h;
   }

我已经计划以完全相同的方式回答; +1 - BorisOkunskiy
@Peter Lawrey,我该如何查看对象的hashCode实现? - lowLatency
@naroji 它在OpenJDK中。不幸的是,有多种策略,而且不清楚使用哪一种。 - Peter Lawrey
我去了OpenJDK的Object类,但那里也被定义为native。http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/tip/src/share/classes/java/lang/Object.java - lowLatency

8

hashCode()方法经常用于标识一个对象。我认为Object类的实现返回对象的指针(不是真正的指针,而是唯一的ID或类似的东西)。但大多数类都会重写这个方法,比如String类。两个String对象可能没有相同的指针,但它们是相等的:

new String("a").hashCode() == new String("a").hashCode()

我认为hashCode()最常用的用途是在HashtableHashSet等中。

Java API对象hashCode()

编辑:(由于最近一次的负投票和我读到的一篇关于JVM参数的文章)

使用JVM参数-XX:hashCode,您可以更改计算hashCode的方式(请参见Java Specialists' Newsletter的222号问题)。

HashCode==0: 返回与对象在内存中的位置无关的随机数。据我所知,种子的全局读写对于具有大量处理器的系统来说不是最优的。

HashCode==1: 计算哈希码值,不确定从什么值开始,但似乎相当高。

HashCode==2: 总是返回1的确切标识哈希码。这可用于测试依赖于对象标识的代码。JavaChampionTest在上面的示例中返回Kirk的URL的原因是所有对象都返回相同的哈希码。

HashCode==3: 从零开始计算哈希码值。它看起来不是线程安全的,因此多个线程可能会生成具有相同哈希码的对象。

HashCode==4: 这似乎与创建对象的内存位置有关。

HashCode>=5: 这是Java 8的默认算法,具有每个线程的种子。它使用Marsaglia的异或移位方案生成伪随机数。


如果我正确地阅读了代码,在Java 8和9中,默认策略是5。 - Stephen C

6

我读到过它是一个对象的内存引用。

不是这样的。Object.hashCode()14年前曾返回内存地址,但现在已经不再是这样了。

那么它是什么类型的值?

这完全取决于你所谈论的类以及它是否重写了`Object.hashCode()`。


@chsdk 这是垃圾回答。它否认了问题中的某个断言。“不”是一个答案。注意,我也没有向任何人请求澄清。 - user207421
这是一个自动评论,在您2小时前的最后一次编辑之前,由于“不是答案”的标记不清楚且未经解释而生成。 - cнŝdk
2
@chsdk 我无法理解“used to”和“not since”中有任何不清楚或未解释的地方。评论是如何到达这里并不重要。 - user207421
嗯......我们已经到了2017年,现在距离21年前已经过去了!! - Not a bug
那么,Object.hashCode() 实际上返回什么? - aneesh joshi
@aneeshjoshi 实际上它返回的是 System.identityHashCode() 的结果,但它是如何计算的并没有指定。 - user207421

5

从OpenJDK源代码(JDK8)中:

使用默认值5生成哈希码:

product(intx, hashCode, 5,                                                
      "(Unstable) select hashCode generation algorithm")       

每个线程都有一个种子,用于生成随机数,同时还有一些常量数据:

// thread-specific hashCode stream generator state - Marsaglia shift-xor form
  _hashStateX = os::random() ;
  _hashStateY = 842502087 ;
  _hashStateZ = 0x8767 ;    // (int)(3579807591LL & 0xffff) ;
  _hashStateW = 273326509 ;

然后,该函数会创建哈希码(默认为上面指定的5):
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 = cast_from_oop<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 = cast_from_oop<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;
}

所以我们可以看到,至少在JDK8中,默认设置为随机线程特定。


3

定义: 字符串hashCode()方法返回字符串的哈希码值,以整数形式表示。

语法: public int hashCode()

哈希码使用下面的公式计算

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

where:

s is ith character in the string
n is length of the string
^ is exponential operand

示例: 例如,如果您想为字符串“abc”计算哈希代码,则我们有以下详细信息。

s[] = {'a', 'b', 'c'}
n = 3

所以哈希码的值将会被计算为:

s[0]*31^(2) + s[1]*31^1 + s[2]
= a*31^2 + b*31^1 + c*31^0
= (ASCII value of a = 97, b = 98 and c = 99)
= 97*961 + 98*31 + 99 
= 93217 + 3038 + 99
= 96354

'abc'的哈希码值为96354


3

如果我没记错的话(请查看java.lang.Object的JavaDoc),Object.hashCode()是与实现有关的,它将根据对象而变化(Sun JVM从对象的引用值中派生该值)。

请注意,如果您正在实现任何非平凡对象,并希望在HashMap或HashSet中正确存储它们,则必须重写hashCode()和equals()。 hashCode()可以做任何您喜欢的事情(完全合法,但返回1是次优的),但是如果您的equals()方法返回true,则hashCode()为两个对象返回的值必须相等。

对hashCode()和equals()的混淆和缺乏理解是错误的主要来源。 确保您彻底熟悉Object.hashCode()和Object.equals()的JavaDocs,我保证花费的时间会物超所值。


1

我很惊讶没有人提到这一点,尽管对于任何非Object类,你的第一步应该是阅读许多类的源代码,.hashcode()只是从Object简单地扩展而来,在这种情况下,根据您的JVM实现可能会发生几件不同的有趣事情。Object.hashcode()调用System.identityHashcode(object)

确实,使用对象内存地址是古老的历史,但许多人没有意识到他们可以通过jvm参数-XX:hashCode=N控制此行为以及如何计算Object.hashcode(),其中N可以是[0-5]之间的数字...

0 – Park-Miller RNG (default, blocking)
1 – f(address, global_statement)
2 – constant 1
3 – serial counter
4 – object address
5 – Thread-local Xorshift

根据应用程序,当调用.hashcode()时,您可能会遇到意外的性能下降,这通常是因为您正在使用其中一个共享全局状态和/或阻塞的算法。


0
根据 JavaDoc 的描述,对象的内部地址会被转换为整数。因此可以清楚地知道 hashCode() 方法并不会返回对象的内部地址。以下提供了相关链接: https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode-- 请参考下面的示例代码来更好理解:
public class HashCodeDemo
    {
    public static void main(String[] args)
        {
        final int CAPACITY_OF_MAP = 10000000;

        /**
         * hashCode as key, and Object as value
         */
        java.util.HashMap<Integer, Object> hm1 = new java.util.HashMap<Integer, Object>(CAPACITY_OF_MAP);
        int noOfDistinceObject = 0;
        Object obj = null;
        for(int i = 0; i < CAPACITY_OF_MAP; i++)
            {
            obj = new Object();
            hm1.put(obj.hashCode(), new Object());
            }
        System.out.println("hm1.size() = "+hm1.size());

        /**
         * hashCode as key, and Object as value
         */
        java.util.HashMap<Integer, Object> hm2 = new java.util.HashMap<Integer, Object>(CAPACITY_OF_MAP);
        for(int i = 0; i < CAPACITY_OF_MAP; i++)
            {
            obj = new Object();
            /**
             * Each Object has unique memory location , 
             * and if Object's hashCode is memory location then hashCode of Object is also unique
             * then no object can put into hm2.
             * 
             * If obj's hashCode is doesn't exists in hm1 then increment noOfDistinceObject , else add obj into hm2.
             */
            if(hm1.get(obj.hashCode()) == null)
                {
                noOfDistinceObject++;
                }
            else
                {
                hm2.put(obj.hashCode(), new Object());
                }
            }

        System.out.println("hm2.size() = "+hm2.size());
        System.out.println("noOfDistinceObject = "+noOfDistinceObject);
        }
    }

每个对象都有唯一的内存位置,如果对象的hashCode方法返回内存位置,则对象的hashCode也是唯一的,但如果我们运行上面的示例代码,则某些对象具有相同的哈希码值,而某些对象具有唯一的哈希码值。
因此,我们可以说Object类中的hashCode方法不返回内存位置。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接