Java JVM在共享字符串数据方面会更聪明吗?
看看这个测试Java JVM在共享字符串数据方面会更聪明吗?,java,string,Java,String,看看这个测试 String s1 = "1234"; String s2 = "123"; Field field = String.class.getDeclaredField("value"); field.setAccessible(true); char[] value1 = (char[]) field.get(s1); char[] value2 = (char[]) field.get(s2); System.out.prin
String s1 = "1234";
String s2 = "123";
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value1 = (char[]) field.get(s1);
char[] value2 = (char[]) field.get(s2);
System.out.println(value1 == value2);
它打印false
,这意味着JVM为s1和s2持有两个不同的字符数组。有人能解释为什么s1和s2不能共享同一个字符数组吗?看起来java.lang.String是为内容共享而设计的,不是吗
注意:我不知道所有的JVM。这是Oracle的Java HotSpot(TM)客户端VM 22.1-b02(JRE 1.7)
更新
另一方面,如果部分共享很少(似乎只适用于String.substring创建的字符串),那么为什么所有字符串都应该有int count
和int offset
字段?它是8个无用字节。这不仅是尺寸,也是创作速度。对象越大,其初始化时间越长。这是一个测试
long t0 = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
new String("xxxxxxxxxxxxx");
}
System.out.println(System.currentTimeMillis() - t0);
大约需要140毫秒
有人能解释为什么s1和s2不能共享同一个字符数组吗
因为“1234”
与“123”
的字符序列不同
有人能解释为什么s1和s2不能共享同一个字符数组吗
他们可以,他们只是不可以,可能是因为JVM启动时间会因为查看部分匹配而受到影响
值得注意的是,在某些情况下,对于非内部字符串,它们可以共享一个字符数组:
String s1 = "1234";
String s2 = s1.substring(0, 3);
…至少通过OpenJDK 6。显然,在(谢谢你教我这个)
有趣的是,如果你实习,Sun的JVM 1.6将它们分开:
String s1 = "1234";
String s2 = s1.substring(0, 3);
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value1 = (char[]) field.get(s1);
char[] value2 = (char[]) field.get(s2);
System.out.println(value1 == value2);
s2 = s2.intern();
value2 = (char[]) field.get(s2);
System.out.println(value1 == value2);
我得到:
true
false
符合事实的
错误的
我想它不喜欢在实习生池中有其他字符串的子集。我认为JVM在实习生字符串时没有达到这个长度的原因是它根本不值得: 按照您的建议,将空间使用降至最低的简单的内部调用实现将具有
O(N^2)
性能,其中N
是JVM生命周期中内部调用的唯一字符串数据的字符数。(好的,它比那复杂一点……但它很贵。)
试图避免O(N^2)
问题的实现通常会使用比共享字符数组所节省的空间更多的空间来避免问题
字符串实现(包括interning)是一种实用的实现,它平衡了相互竞争的关注点,在一系列实际应用程序中求平均值时提供最佳性能。为什么它应该是同一个数组?123和1234是不同的值。因此,如果JVM字符串常量池中有一个“1234”,s2可以将其值指向这个字符数组,设置offset=0;计数=3。不需要新的字符数组。有意义吗?有意义,就内存消耗而言,这将是一个很好的优化。但是,当创建一个新字符串时,JVM必须搜索所有字符串实例,并试图找到与其试图分配的子字符串相匹配的子字符串。这听起来像是一个巨大的开销,IMHO超过了指向内存中相同数组的好处。如果您自己编写一个程序,在为s2创建一个新的字符数组之前,如果您的池中有一个以“123”开头的字符串,那么如果您想进行测试,会有任何开销吗?假设您不创建任何类似索引的结构(这也需要一些内存),那么您必须检查已经分配的每个字符串实例,因此计算复杂度为o(n^2)(分配的字符串数量*字符串中的字母数量)。不是很好。但是AFAIK
“1234”。子字符串(0,2)
实际上可能使用相同的char[]
。不是吗?(编辑:经过测试,它确实使用了相同的char[]
,尽管它的顺序不同。它使用了size
和offset
变量)。#substring()
调用可以引用与this
相同的char[]
,但不能保证它会引用。但是OP的代码不包含#substring()
调用。但在某些情况下,它确实包含调用。因此,我不理解为什么“1234”与“123”不是同一个字符序列这一事实是对这个问题的回答,你能详细说明吗?+1。同样值得一提的是,仅对于字符串文本,它可以由编译器完成,jvm甚至不需要知道这种优化。我特意采用了最简单的情况,即s1和s2都以相同的字符“123”开头。我不敢相信在这种情况下会对启动时间产生任何影响。@amit:实习是由JVM完成的,而不是由编译器完成的。但是是的,编译器可以发出代码来转换Evgeniy的String s2=“123”
intostrings2=s1.子串(0,3)代码>,但我无法想象这是个好主意。@EvgeniyDorofeev,类文件格式使用结构定义字符串常量。该长度紧跟在类文件中的字节数组之前,因此类文件生成器无法组合两个字符串,其中一个字符串是另一个字符串的子字符串。在从UTF8转换到UTF16时进行前缀检查需要构建和遍历一个trie,这样会对启动时间产生一些影响。@T.J.Crowder,当您的JVM允许人们通过Field.setAccessible
变异备份数组时,这是一个特别糟糕的主意。
true
false