如何按照 Joshua Bloch 在 effective java 中的建议在 Java 中缓存哈希码?

问题描述 投票:0回答:4

我有来自 Joshua Bloch 的 effective java 的以下代码(第 9 项,第 3 章,第 49 页)

如果一个类是不可变的,并且计算哈希码的成本是 重要的是,您可以考虑将哈希码缓存在对象中 而不是每次请求时都重新计算。如果你相信 大多数这种类型的对象将被用作哈希键,那么你 应该在创建实例时计算哈希码。 否则,您可能会选择第一次延迟初始化它 hashCode 被调用(第 71 项)。不清楚我们的电话号码 类值得这样处理,但只是为了向您展示它是如何完成的:

    // Lazily initialized, cached hashCode
    private volatile int hashCode;  // (See Item 71)
    @Override public int hashCode() {
        int result = hashCode;
        if (result == 0) {
            result = 17;
            result = 31 * result + areaCode;
            result = 31 * result + prefix;
            result = 31 * result + lineNumber;
            hashCode = result;
        }
        return result;
    }

我的问题是缓存(记住 hashCode)在这里是如何工作的。第一次调用

hashCode()
方法时,没有
hashCode
将其分配给结果。关于缓存如何工作的简短解释会很棒。 谢谢

java caching hashcode effective-java
4个回答
12
投票

简单。请阅读下面我嵌入的评论...

private volatile int hashCode;
//You keep a member field on the class, which represents the cached hashCode value

   @Override public int hashCode() {
       int result = hashCode;
       //if result == 0, the hashCode has not been computed yet, so compute it
       if (result == 0) {
           result = 17;
           result = 31 * result + areaCode;
           result = 31 * result + prefix;
           result = 31 * result + lineNumber;
           //remember the value you computed in the hashCode member field
           hashCode = result;
       }
       // when you return result, you've either just come from the body of the above
       // if statement, in which case you JUST calculated the value -- or -- you've
       // skipped the if statement in which case you've calculated it in a prior
       // invocation of hashCode, and you're returning the cached value.
       return result;
   }

2
投票

实例变量中的

hashCode
变量,并且未显式初始化,因此 Java 将其初始化为
0
(JLS 第 4.12.5 节)
。比较
result == 0
实际上是检查是否已为
result
分配了一个可能非零的哈希码。如果尚未分配,则执行计算,否则仅返回先前计算的哈希码。


0
投票

如其他答案中已经解释的那样更详细一些,因为许多细节都有特定的含义,尤其是在多线程环境中。

// A field to hold the hash code per instance.
// It's volatile to make it available to all threads. (Otherwise, 
// it may happen that the hash code is calculated by one thread, but
// but still not available to other threads, because it remains in
// a cache of a single CPU core.)
private volatile int hashCode;  

@Override public int hashCode() {
    // Read the field into a local variable in order to not conflict 
    // with other threads while in this method.
    int result = hashCode;

    // Check the caches value. 0 means that the hash code has not been
    // calculated yet.
    if (result == 0) {

        // Calculate the hash code, keeping in in a local variable until 
        // it's finished, to avoid that other threads read an incomplete 
        // value.
        // It's possible that more than one thread calculate the hash code
        // simultanoulsy, which could be avoided when it was synchronized.
        // In most applications, it performs better when no synchronizing is
        // required when accessing an already calculated hash code, which is
        // expected to happen much more often compared to the unlikely case
        // that two (or more) threads calculate the hash code simulatanoulsy.

        // Initialized with 17 also makes sure that the value won't be 0
        // after calculation.
        result = 17;
        result = 31 * result + areaCode;
        result = 31 * result + prefix;
        result = 31 * result + lineNumber;

        // The hash code is written back to the volatile field to make it
        // available to future calls.
        hashCode = result;
    }

    // Return the hash code.
    return result;
}

最后一些注意事项:

  • 即使在非多线程环境下,这种实现也没有任何问题。
  • 哈希码在对象的生命周期中不应该改变。因此,请确保您不会访问稍后可能会更改的字段(计算哈希码时总是如此,但在缓存哈希码时更明显)。
  • 哈希码计算预计仍会相当快。可以多次计算。否则同步可能有意义。
  • 不要在其他领域尝试类似的事情。没有同步的多线程风险很大,通常不值得冒这个风险。

-1
投票

如果您确实希望它正常工作,您可以放置另一个名为 isHashInvalid 的易失性变量布尔值。每个涉及哈希函数中访问的值的设置器都会设置此变量。然后就变成了,(现在不需要测试“0”):

private volatile int isHashInvalid=TRUE;
private volatile int hashCode; //Automatically zero but it doesn't matter

//You keep a member field on the class, which represents the cached hashCode value
@Override public int hashCode() {
    int result = hashCode;
    if (isHashInvalid) {
       result = 17;
       result = 31 * result + areaCode;
       result = 31 * result + prefix;
       result = 31 * result + lineNumber;
       //remember the value you computed in the hashCode member field
       hashCode = result;
       isHashInvalid=FALSE;
    }
    // when you return result, you've either just come from the body of the above
    // if statement, in which case you JUST calculated the value -- or -- you've
    // skipped the if statement in which case you've calculated it in a prior
    // invocation of hashCode, and you're returning the cached value.
    return result;
}
© www.soinside.com 2019 - 2024. All rights reserved.