Java 为什么库喜欢在edge案例中对不可变对象重新计算哈希代码?
在番石榴代码(我认为这是高质量代码的一个示例)中,我发现了以下片段:Java 为什么库喜欢在edge案例中对不可变对象重新计算哈希代码?,java,caching,guava,immutability,hashcode,Java,Caching,Guava,Immutability,Hashcode,在番石榴代码(我认为这是高质量代码的一个示例)中,我发现了以下片段: // If the cachedHashCode is 0, it will always be recalculated, unfortunately. private transient int cachedHashCode; public final int hashCode() { // Racy single-check. int code = cachedHashCode; if (code ==
// If the cachedHashCode is 0, it will always be recalculated, unfortunately.
private transient int cachedHashCode;
public final int hashCode() {
// Racy single-check.
int code = cachedHashCode;
if (code == 0) {
cachedHashCode = code = element.hashCode();
}
return code;
}
因此“如果cachedHashCode为0,很遗憾,它将始终被重新计算”。另一个例子是JDKString.hashCode
:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
虽然在一般情况下,它会略微减慢hashCode
的计算速度,但这种技巧可以避免最坏的情况,因为它会反复(并且缓慢地(例如,长字符串))计算。
为什么它不在guavaImmutableSet
或JDKString
中使用
编辑
最近的Java 7版本添加了自定义String.hash32
实现,其中包含对这种特殊情况的处理:
// ensure result is not zero to avoid recalcing
h = (0 != h) ? h : 1;
这样做是为了节省空间 例如,如果
String
没有使用散列值零表示(未缓存),那么String
类将需要一个额外的布尔标志来表示散列值未缓存
因此,取舍是每次重新计算哈希值的概率为40亿分之一,而不是每个字符串对象多计算一个字
1-假设从所有可能的Java字符串域中随机选择字符串对象。真正的程序不是那样工作的。。。但关键是,重新计算哈希代码的影响不太可能很大,除非您有意为此目的设计应用程序。为“\0”等字符串重新计算哈希代码的惩罚可能会重复,这可能太小,不值得使用大学风格/人为的代码,例如“if(hash==0)“除了希望保持与任何持久化字符串哈希值的兼容性外,让哈希代码逻辑检查非空字符串的计算值是否为零,如果是,则增加它,是否会有任何问题?根据优化器的不同,这将归结为1-3条快速指令,并将避免潜在的灾难性最坏情况行为。这些1-3条指令将在每次(新)字符串散列时执行。而散列空字符串的成本也没有那么大。(这是一个灾难性的夸大其词!)您提出的优化可能会导致大多数用例的速度减慢。散列空字符串的成本很低,这就是为什么空字符串返回零不会成为问题的原因。可能是灾难性的是,重复查找某些大字符串比查找其他字符串花费的时间要长几个数量级。@supercat-但假设字符串是随机选择的,则出现坏情况的概率非常非常低。令人担忧的是长字符串被精心设计成哈希值为零。。。为了使某物破裂。这是一个特例,可以用其他方式处理。。。如果情况需要的话。
// ensure result is not zero to avoid recalcing
h = (0 != h) ? h : 1;