6

我已经阅读了很多关于创建 String 时内存分配的冲突文章。有些文章说 new 运算符在堆中创建一个字符串,而字符串字面量是在字符串池 [Heap] 中创建的,而有些文章说 new 运算符在堆中创建一个对象,在字符串池中创建另一个对象。

为了分析这一点,我编写了以下程序,该程序打印 String char 数组和 String 对象的哈希码:

import java.lang.reflect.Field;

public class StringAnalysis {

    private int showInternalCharArrayHashCode(String s)
            throws SecurityException, NoSuchFieldException,
            IllegalArgumentException, IllegalAccessException {
        final Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        return value.get(s).hashCode();
    }

    public void printStringAnalysis(String s) throws SecurityException,
            IllegalArgumentException, NoSuchFieldException,
            IllegalAccessException {
        System.out.println(showInternalCharArrayHashCode(s));

        System.out.println(System.identityHashCode(s));

    }

    public static void main(String args[]) throws SecurityException,
            IllegalArgumentException, NoSuchFieldException,
            IllegalAccessException, InterruptedException {
        StringAnalysis sa = new StringAnalysis();
        String s1 = new String("myTestString");
        String s2 = new String("myTestString");
        String s3 = s1.intern();
        String s4 = "myTestString";

        System.out.println("Analyse s1");
        sa.printStringAnalysis(s1);

        System.out.println("Analyse s2");
        sa.printStringAnalysis(s2);

        System.out.println("Analyse s3");
        sa.printStringAnalysis(s3);

        System.out.println("Analyse s4");
        sa.printStringAnalysis(s4);

    }

}

该程序打印以下输出:

Analyse s1
1569228633
778966024
Analyse s2
1569228633
1021653256
Analyse s3
1569228633
1794515827
Analyse s4
1569228633
1794515827

从这个输出中可以清楚地看出,无论 String 是如何创建的,如果 String 具有相同的值,那么它们共享相同的 char 数组。

现在我的问题是这个 chararray 存储在哪里,是存储在堆中还是存储在 permgen 中?我还想了解如何区分堆内存地址和永久内存地址。

如果它存储在 permgen 中,我有一个大问题,因为它会占用我宝贵的有限 permgen 空间。如果 char 数组没有存储在 permgen 中,而是存储在堆中,那么这是否意味着字符串文字也使用堆空间 [这是我从未读过的东西]。

4

3 回答 3

3

从字符串 src

 public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

很明显,使用此构造函数创建的字符串与原始字符串共享 char 数组(值)。

需要注意的是,API 不保证这种共享:

初始化一个新创建的 String 对象,使其表示与参数相同的字符序列;换句话说,新创建的字符串是参数字符串的副本。除非需要原始的显式副本,否则不需要使用此构造函数,因为字符串是不可变的

例如,String.substring 用于与原始字符串共享 char 数组,但在 Java 1.7 的最新版本中,String.substring 会复制 char 数组。

于 2013-04-22T17:20:26.877 回答
2

从这个输出中可以清楚地看出,无论 String 是如何创建的,如果 String 具有相同的值,那么它们共享相同的 char 数组

不完全是:这是因为您从一个文字字符串开始,并从中创建多个实例。在 OpenJDK (Sun/Oracle) 实现中,如果支持数组表示整个字符串,它将被复制。您可以在src.jar或此处查看此内容:http: //grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/String.java#String.%3Cinit% 3E%28java.lang.String%29

如果您仔细构建源字符串,使它们从不同的字符数组开始,您会发现它们不共享支持数组。

现在我的问题是这个字符数组存储在哪里

据我所知,字符串文字的字符数组存储在堆上(那些对类加载内部有更好了解的人,请随时发表评论)。从文件加载的字符串将始终将它们的后备数组存储在堆上。

我确实知道的是,所使用的数据结构intern()只引用了String对象,而不是它的字符数组。

于 2013-04-22T17:16:25.363 回答
1

最后一个:根据定义,字面量“myTestString”是实习的,所有具有相同值的实习 String 引用都指向同一个物理 String 对象。因此,文字将与intern.

[更正] 根据定义,具有相同字符序列值的两个字符串的 hashCode(但不是 identityHashCode)将是相同的。

char[]另一方面,数组的 hashCode只是其地址位的混乱,与数组的内容无关。这表明该value数组在所有上述情况下都是完全相同的数组。

(更多信息:String 的旧实现包括指向 a 的指针char[]、偏移量、长度和 hashCode 值。较新的实现不推荐使用偏移值,String 值从数组的元素 0 开始。其他(非 Sun /non-Oracle) 实现取消了单独的char[]数组,并将字符串字节包含在主堆分配中。不要求该value字段实际存在。)

[继续] 复制测试用例并添加几行。hashCode 和 identityHashCode 在给定的 上产生相同的值char[],但在具有相同内容的不同数组上产生不同的值。

s1 和 s2 中的数组相同的事实几乎可以肯定是因为它们共享char[]内部文字“myTestString”的数组。如果字符串是从“新”数组单独构造的,char[]它们将是不同的。

所有这一切的主要内容是 String 字面量是实习的,并且当使用new String(String).

Char array hash codes
a1.hashCode() = 675303090
a2.hashCode() = 367959235
a1 identityHashCode = 675303090
a2 identityHashCode = 367959235
Strings from char arrays
a1 String = ABCDE
a1 String's hash = 62061635
a1 String value's identityHashCode = 510044439
a2 String = ABCDE
a2 String's hash = 62061635
a2 String value's identityHashCode = 1709651096
于 2013-04-22T17:15:55.243 回答