十年后,一切都变了。我真的不敢相信这一点(但是内心的极客非常开心)。
正如您所注意到的那样,有些字符串的String :: hashCode
为0
,而这个值之前没有被缓存(稍后会涉及到)。许多人争论(包括在这个问题的问答中)为什么没有在java.lang.String
中增加一个字段,比如说:hashAlreadyComputed
,然后直接使用它。问题很明显:每个String实例需要额外的空间。顺便说一下,java-9
引入了compact String
,有原因的,因为许多基准测试表明,这是一个相当(过度)使用的类,在大多数应用程序中都是如此。再添加更多的空间?决策是:不要。特别是最小可能的添加将是1字节
,而不是1位
(对于32位JMV
,额外空间将是8字节
:1个标志位,7个对齐位)。
因此,在java-9
中出现了Compact String
,如果您仔细观察(或关心),他们确实在java.lang.String
中添加了一个字段:coder
。我不是刚反对过这个吗?事情并没有那么简单。似乎紧凑字符串的重要性超过了“额外空间”的论点。还要说的是,额外空间只对32位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字节
间隙。当添加
coder
时,它只需要使用
1字节
而不需要额外的空间。因此,在添加了
Compact 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 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个字节
,间隙缩小到了
3个字节
,这个“损坏”已经在
jdk-9
中发生了。对于
32位JVM
,增加了
8个字节:1 coder + 7个间隙
,而对于
64位JVM
,没有增加,
coder
占用了一些空间来填补间隙。
现在,在 jdk-13
中他们决定利用这个 间隙
,既然它已经存在了。让我提醒你,具有零 hashCode 的字符串的概率是 40 亿分之一;但仍有人说:那又怎样?我们来解决一下!于是乎: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;
}
即使我读了源代码下的注释:
只有在阅读了
这篇文章之后,它才变得有意义。这很棘手,但只能一次写入一条,以上讨论中有更多细节。