Java 为什么';t字串';s hashCode()缓存0?
我注意到在字符串的Java6源代码中,hashCode只缓存0以外的值。以下代码片段显示了性能上的差异:Java 为什么';t字串';s hashCode()缓存0?,java,string,hashcode,Java,String,Hashcode,我注意到在字符串的Java6源代码中,hashCode只缓存0以外的值。以下代码片段显示了性能上的差异: public class Main{ static void test(String s) { long start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { s.hashCode(); } System.out.forma
public class Main{
static void test(String s) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
s.hashCode();
}
System.out.format("Took %d ms.%n", System.currentTimeMillis() - start);
}
public static void main(String[] args) {
String z = "Allocator redistricts; strict allocator redistricts strictly.";
test(z);
test(z.toUpperCase());
}
}
因此,我的问题是:
- 为什么字符串的hashCode()不能缓存0
- Java字符串散列为0的概率是多少
- 避免每次为散列为0的字符串重新计算散列值的性能损失的最佳方法是什么
- 这是缓存值的最佳实践方式吗?(即缓存除一个之外的所有内容?)
有趣的是,这里的每一行都是一个散列为0的字符串:
pollinating sandboxes
amusement & hemophilias
schoolworks = perversive
electrolysissweeteners.net
constitutionalunstableness.net
grinnerslaphappier.org
BLEACHINGFEMININELY.NET
WWW.BUMRACEGOERS.ORG
WWW.RACCOONPRUDENTIALS.NET
Microcomputers: the unredeemed lollipop...
Incentively, my dear, I don't tessellate a derangement.
A person who never yodelled an apology, never preened vocalizing transsexuals.
它使用0表示“我还没有计算出hashcode”。另一种方法是使用单独的布尔标志,这将占用更多内存。(当然,也可以不缓存哈希代码。)
我不希望有很多字符串散列为0;可以说,哈希例程有意避免0是有意义的(例如,将哈希值0转换为1,并缓存该值)。这将增加碰撞,但避免重新灰化。但是现在这样做已经太迟了,因为字符串哈希代码算法已经被明确地记录下来了 至于这在总体上是否是一个好主意:它确实是一种高效的缓存机制,而且(请参见编辑)如果进行更改,可能会更好,以避免对值进行重新灰化,从而导致散列值为0。就我个人而言,我很想看到一些数据,这些数据让Sun相信这是值得一做的——它会为每一个创建的字符串占用额外的4字节,不管它被散列的频率是多少,唯一的好处是对于多次散列的字符串
编辑:正如KevinB在其他地方的评论中指出的,上面的“避免0”建议可能会有净成本,因为它有助于一种非常罕见的情况,但需要对每个哈希计算进行额外的比较。0不会被缓存,因为实现将缓存值0解释为“缓存值尚未初始化”。另一种选择是使用
java.lang.Integer
,其中null表示尚未缓存该值。然而,这将意味着额外的存储开销
关于字符串的散列码被计算为0的概率,我认为概率很低,在以下情况下可能发生:
- 字符串为空(尽管每次重新计算此哈希代码实际上是O(1))
- 发生溢出时,最终计算的哈希代码为0(
),例如Integer.MAX_值+h(c1)+h(c2)+…h(cn)==0
- 该字符串仅包含Unicode字符0。非常不可能,因为这是一个除了在“纸带世界”(!)中没有意义的控制字符:
- 为什么字符串的hashCode()不能缓存0
- Java字符串散列为0的概率是多少
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
使用int
算术,其中s[i]
是字符串的第i个字符,n
是字符串的长度。(作为特殊情况,空字符串的哈希值定义为零。)
我的直觉是,如上所述的hashcode函数在int
值的范围内提供了一个统一的字符串散列值分布。均匀排列意味着随机生成的字符串散列为零的概率为1/2^32
- 避免每次为散列为0的字符串重新计算散列值的性能损失的最佳方法是什么
- 这是缓存值的最佳实践方式吗?(即缓存除一个之外的所有内容?)
- 为每个String对象添加一个
标志,使每个Java字符串都有一个额外的单词cached
- 使用
成员的顶部位作为缓存标志。这样可以缓存所有散列值,但只有可能的字符串散列值的一半散列
- 根本不要在字符串上缓存哈希代码
(请注意,有两个“公共”字符串值散列为零:空字符串和仅由NUL字符组成的字符串。但是,与计算典型字符串值的散列代码相比,计算这些值的散列代码的成本很小。)您不必担心任何问题。这里有一个思考这个问题的方法 假设您有一个应用程序,它一年四季都在散列字符串。假设它需要1000个字符串,全部在内存中,以循环方式重复调用hashCode(),经过一百万次,然后再获得1000个新字符串,然后再次执行 假设一个字符串的散列码为零的可能性实际上远远大于1/2^32。我肯定它比1/2^32大一些,但假设它比1/2^16更糟糕(平方根!现在更糟糕了!) 在这种情况下,Oracle的工程师改进了这些字符串的哈希代码的缓存方式,这比其他任何人都能让您受益匪浅。那么你呢
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
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;
}
if (h == 0 && value.length > 0) ...
java.lang.String object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 char[] String.value N/A
16 4 int String.hash N/A
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Space losses : ... 4 bytes total.
java.lang.String object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 byte[] String.value N/A
16 4 int String.hash N/A
20 1 byte String.coder N/A
21 3 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
java.lang.String object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 byte[] String.value N/A
16 4 int String.hash N/A
20 1 byte String.coder N/A
21 1 boolean String.hashIsZero N/A
22 2 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total
public int hashCode() {
int h = hash;
if (h == 0 && !hashIsZero) {
h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
if (h == 0) {
hashIsZero = true;
} else {
hash = h;
}
}
return h;
}
@Override
public int hashCode(){
if(!hashCodeComputed){
// or any other sane computation
hash = 42;
hashCodeComputed = true;
}
return hash;
}
// The hash or hashIsZero fields are subject to a benign data race,
// making it crucial to ensure that any observable result of the
// calculation in this method stays correct under any possible read of
// these fields. Necessary restrictions to allow this to be correct
// without explicit memory fences or similar concurrency primitives is
// that we can ever only write to one of these two fields for a given
// String instance, and that the computation is idempotent and derived
// from immutable state