【问题标题】:How is hashCode() calculated in JavaJava中hashCode()是如何计算的
【发布时间】:2010-03-11 18:32:05
【问题描述】:

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

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

我很困惑:是 ASCII 还是什么类型的值?

【问题讨论】:

    标签: java hashcode


    【解决方案1】:

    hashCode() 返回的值绝不保证是对象的内存地址。我不确定Object 类中的实现,但请记住,大多数类将覆盖hashCode(),这样语义上等价的两个实例(但不是同一个实例)将散列到相同的值。如果类可以在依赖于hashCodeequals 一致的其他数据结构(例如Set)中使用,这一点尤其重要。

    无论如何,没有hashCode() 可以唯一标识对象的实例。如果您想要基于底层指针的哈希码(例如在 Sun 的实现中),请使用 System.identityHashCode() - 这将委托给默认的 hashCode 方法,无论它是否已被覆盖。

    尽管如此,即使System.identityHashCode() 也可以为多个对象返回相同的哈希值。有关解释,请参阅 cmets,但这里有一个示例程序,它不断生成对象,直到找到两个具有相同 System.identityHashCode() 的对象。当我运行它时,它会很快找到两个匹配的System.identityHashCode()s,平均在将大约 86,000 个 Long 包装器对象(以及键的整数包装器)添加到映射后。

    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
    

    【讨论】:

    • 有什么特别的原因吗?如果这里有什么不正确的地方,我希望得到更正,但不加解释地投反对票不会增加讨论。
    • 几年前,Ted Neward 在blogs.tedneward.com/2008/07/16/… 中解释了 OpenJDK 如何实现 Object.hashCode()。 OpenJDK 从对象地址派生散列码,但缓存此值并将其返回给后续调用者,以防对象在内存中移动并且其地址发生更改。在简要回顾了最新的代码后,我发现自 Neward 写他的文章以来,实现似乎并没有改变。
    • 这似乎支持我的回答。
    • 这不是意味着移动的对象哈希码可能会碰到另一个新对象的哈希码吗?
    • 是的:它可以“命中”另一个新对象的哈希码。哈希码不应被认为是唯一的。 “哈希码”旨在“缩小”唯一性(用于哈希表),但您必须始终使用“等于”。
    【解决方案2】:

    哈希码是一个整数值,表示调用它的对象的状态。这就是为什么设置为 1 的Integer 将返回哈希码“1”,因为Integer's 哈希码和它的值是相同的。一个字符的哈希码等于它的 ASCII 字符码。如果您编写自定义类型,您有责任创建一个良好的 hashCode 实现,该实现最能代表当前实例的状态。

    【讨论】:

      【解决方案3】:

      如果您想知道它们是如何实现的,我建议您阅读源代码。如果您使用的是 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;
         }
      

      【讨论】:

      【解决方案4】:

      hashCode() 方法通常用于识别对象。我认为 Object 实现返回对象的指针(不是真正的指针,而是唯一的 id 或类似的东西)。但是大多数类会覆盖该方法。就像String 类。两个 String 对象有不同的指针但它们是相等的:

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

      我认为hashCode() 最常见的用法是HashtableHashSet 等。

      Java API Object hashCode()

      编辑:(由于最近的反对票和基于我阅读的一篇关于 JVM 参数的文章)

      使用 JVM 参数 -XX:hashCode,您可以更改 hashCode 的计算方式(请参阅 Java 专家时事通讯的 Issue 222)。

      HashCode==0:只返回与位置无关的随机数 在内存中找到对象。据我所知,全球 种子的读写对于具有大量 处理器。

      HashCode==1:对哈希码值进行计数,不确定是什么值 他们开始,但似乎相当高。

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

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

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

      HashCode>=5:这是 Java 8 的默认算法,具有 每线程种子。它使用 Marsaglia 的 xor-shift 方案来生成 伪随机数。

      【讨论】:

      • 如果我正确阅读代码,在 Java 8 和 9 中默认策略是 5。
      【解决方案5】:

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

      没有。 Object.hashCode() 用于返回大约 14 年前的内存地址。从那以后就没有了。

      什么类型的值

      它是什么完全取决于你在谈论什么类以及它是否覆盖了`Object.hashCode()。

      【讨论】:

      • @chsdk 垃圾。它驳斥了问题中的断言。 “不”是一个答案。注意我也没有要求任何人澄清。
      • 这是一条自动评论,在标记为“不是答案”之后,在您 2 小时前的最后一次编辑之前不清楚且无法解释。
      • @chsdk 我无法辨别任何关于“过去”和“不再”的“不清楚”或“无法解释”。评论是如何到达这里的无关紧要。
      • hmmm...我们在 2017 年,现在是 21 年前!!
      • 那么,Object.hashCode() 究竟返回了什么?
      【解决方案6】:

      来自 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 ;
      

      然后,此函数创建 hashCode(如上指定默认为 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 中默认设置为随机线程特定。

      【讨论】:

        【解决方案7】:

        定义:String hashCode()方法以Integer形式返回String的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
        

        所以hashcode值会被计算为:

        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

        【讨论】:

          【解决方案8】:

          Object.hashCode(),如果内存服务正确(检查 java.lang.Object 的 JavaDoc),是依赖于实现的,并且会根据对象而改变(Sun JVM 从引用的值派生值到对象)。

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

          对 hashCode() 和 equals() 的混淆和缺乏理解是 bug 的一大来源。确保您完全熟悉 Object.hashCode() 和 Object.equals() 的 JavaDocs,我保证所花费的时间会有所回报。

          【讨论】:

            【解决方案9】:

            来自 Javadoc:

            在合理的情况下,Object 类定义的 hashCode 方法确实为不同的对象返回不同的整数。 (这通常通过将对象的内部地址转换为整数来实现,但 Java™ 编程语言不需要这种实现技术。)

            https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode--

            【讨论】:

              【解决方案10】:

              我很惊讶没有人提到这一点,但尽管对于任何非 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() 时看到意外的性能下降,当这种情况发生时,您很可能正在使用共享全局状态和/或块的算法之一。

              【讨论】:

                猜你喜欢
                • 2020-01-26
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-09-12
                • 1970-01-01
                相关资源
                最近更新 更多