0

看看这个测试

    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 保存了两个不同的 char 数组。谁能解释为什么 s1 和 s2 不能共享同一个字符数组?似乎 java.lang.String 是为内容共享而设计的,不是吗?

注意:我不了解所有 JVM。这是 Oracle 的 Java HotSpot(TM) 客户端 VM 22.1-b02 (JRE 1.7)。

更新

另一方面,如果部分共享很少见(似乎仅适用于由 String.substring 创建的字符串),那么为什么所有字符串都应该具有int countint offset字段?它是 8 个无用字节。而这不仅是大小,也是创作速度。对象越大,初始化的时间就越长。这是一个测试

    long t0 = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i++) {
        new String("xxxxxxxxxxxxx");
    }
    System.out.println(System.currentTimeMillis() - t0);

大约需要 200 毫秒。如果我使用这个类

class String2 {
    char[] value;
    String2(String2 s) {
        value = s.value;
    }
}

大约需要 140 毫秒。

4

3 回答 3

5

谁能解释为什么 s1 和 s2 不能共享同一个字符数组?

他们可以,他们只是没有,可能是因为 JVM 启动时间会受到通过字符串intern查找部分匹配的影响。

值得注意的是,对于非实习字符串,它们可以共享一个 char 数组,在某些情况下:

String s1 = "1234";
String s2 = s1.substring(0, 3);

...至少通过 OpenJDK 6。显然,在OpenJDK7 中,它们不再共享 (感谢Marko Topolnik在这里教我)

有趣的是,如果你实习的话,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);

我得到:

真的
错误的

我猜它不喜欢在实习生池中有字符串是其他字符串的子集。

于 2012-12-10T15:39:33.593 回答
4

谁能解释为什么 s1 和 s2 不能共享同一个字符数组?

因为"1234"与 的字符序列不同"123"

于 2012-12-10T15:38:35.667 回答
0

我的看法是 JVM 在实习字符串时没有达到那个长度的原因是它根本不值得:

一个幼稚的实习实现可以最大限度地减少您建议的空间使用量,其O(N^2)性能将N是在 JVM 的生命周期中实习的唯一字符串数据的字符数。(好吧,它比那复杂一点......但它很贵。)

试图避免该O(N^2)问题的实现通常最终会使用比共享字符数组节省的更多空间来避免该问题。

String 实现(包括实习)是一种实用的实现,它平衡了相互竞争的关注点,以便在对一系列实际应用程序进行平均时提供最佳性能。

于 2012-12-16T07:43:42.020 回答