十年后,情况发生了变化。老实说,我真不敢相信(但是我内心的怪胎非常高兴)。
正如您已经指出的String::hashCode
,有些字符串可能存在某些位置,zero
并且没有被缓存(可以做到这一点)。很多人争辩(包括在此问答中)为什么在中没有添加字段java.lang.String
,例如:,hashAlreadyComputed
然后简单地使用它。问题很明显:每个String实例都有多余的空间。有BTW一个理由 java-9
引入compact String
s很简单,因为许多基准测试表明,在大多数应用程序中,这是一个相当(过度)使用的类。增加更多空间?决定是:否。特别是由于最小的可能的加法是1 byte
,而不是1 bit
(对于32 bit JMV
,额外的空间本来是8 bytes
:1表示标志,7表示对齐)。
因此,Compact String
出现了java-9
,如果您仔细看(或关心),他们确实在中添加了一个字段java.lang.String
:coder
。我不是只是反对吗?它不是那么容易。紧凑字符串的重要性似乎超出了“多余空间”的论点。同样重要的是要说多余的空间32 bits VM
仅是重要的(因为对齐没有间隙)。相反,在jdk-8
布局中java.lang.String
是:
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对象都是对齐的(多少取决于JVM和某些启动标志UseCompressedOops
,例如),String
所以之间有一个缺口4 bytes
,未使用。因此,在添加时coder
,1 byte
无需添加额外的空间即可。这样,之后 Compact String
添加s,布局已更改:
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
coder
吃1 byte
了,差距缩小了3 bytes
。因此,“损坏”已经发生jdk-9
。因为-有32 bits JVM
增加,8 bytes : 1 coder + 7 gap
而64 bit JVM
-没有增加,因此coder
从差距中占据了一些空间。
现在,jdk-13
他们决定利用that gap
,因为它仍然存在。让我提醒您,具有零hashCode的String的概率为40亿分之一;仍然有人说:那又怎样?让我们解决这个问题!Voilá:jdk-13
布局java.lang.String
:
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
这里是: boolean String.hashIsZero
。它在代码库中:
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;
}
等待!h == 0
和 hashIsZero
田野?那不应该这样命名:hashAlreadyComputed
?为什么实现不符合以下要求:
@Override
public int hashCode(){
if(!hashCodeComputed){
hash = 42;
hashCodeComputed = true;
}
return hash;
}
即使我阅读了源代码下的注释:
只有在阅读这篇文章后,才有意义。有点棘手,但这一次只写一个,在上面的讨论中有很多细节。