java - 字符串文字池是对字符串对象的引用的集合,还是对象的集合

标签 java string memory-management

在阅读了 SCJP Tip Line 的作者 Corey McGlone 在 javaranch 网站上的文章后,我感到很困惑。命名字符串,字面意思和
Kathy Sierra(javaranch 的联合创始人)和 Bert Bates 编写的 SCJP Java 6 程序员指南。

我将尝试引用 Corey 先生和 Kathy Sierra 女士所引用的有关字符串文字池的内容。

1. 根据 Corey McGlone 先生的说法:

  • 字符串文字池是指向字符串对象的引用集合。
  • String s = "Hello"; (假设 Heap 上没有名为“Hello”的对象),
    将创建一个 String 对象 "Hello"在堆上,并将在字符串文字池(常量表)中放置对此对象的引用
  • String a = new String("Bye"); (假设 Heap 上没有名为“Bye”的对象,new 运算符将强制 JVM 在 Heap 上创建一个对象。

  • 现在解释"new"用于创建字符串及其引用的运算符在本文中有点困惑,因此我将代码和
    文章本身的解释如下。
    public class ImmutableStrings
    {
        public static void main(String[] args)
        {
            String one = "someString";
            String two = new String("someString");
    
            System.out.println(one.equals(two));
            System.out.println(one == two);
        }
    }
    

    在这种情况下,由于关键字 "new.",我们实际上最终得到了稍微不同的行为。
    在这种情况下,对两个字符串字面量的引用仍被放入常量表(字符串字面量池)中,
    但是,当您遇到关键字 "new," 时JVM 有义务在运行时创建一个新的 String 对象,
    而不是使用常量表中的那个。

    这是解释它的图表..

    enter image description here

    那么这是否意味着 String Literal Pool 也有对这个 Object 的引用?

    这是 Corey McGlone 文章的链接

    http://www.javaranch.com/journal/200409/Journal200409.jsp#a1

    2. 根据 SCJP 书中 Kathy Sierra 和 Bert Bates 的说法:
  • 为了使 Java 内存效率更高,JVM 预留了一个称为“字符串常量池”的特殊内存区域,当编译器
    遇到字符串文字,它会检查池以查看是否已存在相同的字符串。如果不是,那么它会创建一个
    新的字符串文字对象。
  • String s = "abc";//创建一个 String 对象和一个引用变量....

    没关系,但后来我被这句话搞糊涂了:
  • String s = new String("abc")//创建两个对象和一个引用变量。

    它在书中说....一个新的 String 对象 in normal(non-pool) memory ,并且“s”将引用它...而
    额外的文字“abc”将被放置在池中。

    书中的上述几行与 Corey McGlone 的文章中的那一句相冲突。
  • 如果 String Literal Pool 是 Corey McGlone 提到的 String 对象引用的集合,那么为什么要将字面量对象“abc”放入池中(如书中所述)?
  • 这个字符串文字池在哪里?

  • 请清除这个疑问,虽然在编写代码时不会有太大影响,但从内存管理方面来说非常重要,并且
    这就是我想清除这个基金的原因。

    最佳答案

    我认为这里要理解的要点是 String 之间的区别。 Java 对象及其内容 - char[]private value field . String基本上是围绕 char[] 的包装器数组,封装它并使其无法修改,所以 String可以保持不变。还有 String class 记住实际使用了这个数组的哪些部分(见下文)。这一切都意味着您可以拥有两个不同的 String对象(相当轻量级)指向同一个 char[] .

    我将向您展示一些示例,以及 hashCode()每个StringhashCode()内部char[] value字段(我将其称为文本以将其与字符串区分开来)。最后我会展示javap -c -verbose输出,以及我的测试类的常量池。请不要将类常量池与字符串文字池混淆。它们并不完全相同。另见 Understanding javap's output for the Constant Pool .

    先决条件

    出于测试目的,我创建了这样一个实用方法,它会中断 String封装:

    private int showInternalCharArrayHashCode(String s) {
        final Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        return value.get(s).hashCode();
    }
    

    它将打印 hashCode()char[] value ,有效地帮助我们了解这个特殊的 String指向同一个char[]文字与否。

    一个类中的两个字符串文字

    让我们从最简单的例子开始。

    Java代码
    String one = "abc";
    String two = "abc";
    

    顺便说一句,如果你只是写 "ab" + "c" ,Java编译器会在编译时进行拼接,生成的代码完全一样。这仅在编译时已知所有字符串时才有效。

    类常量池

    每个类(class)都有自己的constant pool - 一个常量值列表,如果它们在源代码中出现多次,则可以重用。它包括常见的字符串、数字、方法名称等。

    下面是我们上面例子中常量池的内容。
    const #2 = String   #38;    //  abc
    //...
    const #38 = Asciz   abc;
    

    需要注意的重要一点是 String 之间的区别常量对象 ( #2 ) 和 Unicode 编码文本 "abc" ( #38 ) 字符串指向的。

    字节码

    这是生成的字节码。请注意,onetwo引用分配了相同的 #2常量指向 "abc"字符串:
    ldc #2; //String abc
    astore_1    //one
    ldc #2; //String abc
    astore_2    //two
    

    输出

    对于每个示例,我正在打印以下值:
    System.out.println(showInternalCharArrayHashCode(one));
    System.out.println(showInternalCharArrayHashCode(two));
    System.out.println(System.identityHashCode(one));
    System.out.println(System.identityHashCode(two));
    

    两对相等也就不足为奇了:
    23583040
    23583040
    8918249
    8918249
    

    这意味着不仅两个对象都指向相同的 char[] (下面相同的文字)所以 equals()测试将通过。但更重要的是,onetwo是完全相同的引用!所以one == two也是如此。显然如果 onetwo指向同一个对象然后 one.valuetwo.value必须相等。

    文字和 new String()
    Java代码

    现在我们都在等待的例子 - 一个字符串文字和一个新的 String使用相同的文字。这将如何运作?
    String one = "abc";
    String two = new String("abc");
    

    事实"abc"源代码中使用了两次常量应该给你一些提示......

    类常量池

    和上面一样。

    字节码
    ldc #2; //String abc
    astore_1    //one
    
    new #3; //class java/lang/String
    dup
    ldc #2; //String abc
    invokespecial   #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V
    astore_2    //two
    

    仔细看!第一个对象的创建方式与上述相同,这并不奇怪。它只需要一个对已经创建的常量引用 String ( #2 ) 来自常量池。然而,第二个对象是通过正常的构造函数调用创建的。但!第一String作为参数传递。这可以反编译为:
    String two = new String(one);
    

    输出

    输出有点令人惊讶。第二对,代表对 String 的引用对象是可以理解的 - 我们创建了两个 String对象 - 一个是在常量池中为我们创建的,第二个是为 two 手动创建的.但是为什么,第一对实际上表明两者都String对象指向同一个 char[] value大批?!
    41771
    41771
    8388097
    16585653
    

    当您查看如何 String(String) constructor works 时,您就清楚了。 (这里大大简化):
    public String(String original) {
        this.offset = original.offset;
        this.count = original.count;
        this.value = original.value;
    }
    

    看?创建新时 String基于现有对象,它 重复使用 char[] value . String s 是不可变的,不需要复制已知永远不会修改的数据结构。

    我认为这是您问题的线索:即使您有两个 String对象,它们可能仍然指向相同的内容。正如你所看到的 String对象本身很小。

    运行时修改和 intern()
    Java代码

    假设您最初使用了两个不同的字符串,但经过一些修改后它们都相同:
    String one = "abc";
    String two = "?abc".substring(1);  //also two = "abc"
    

    Java 编译器(至少是我的)不够聪明,无法在编译时执行此类操作,请看:

    类常量池

    突然间,我们得到了两个指向两个不同常量文本的常量字符串:
    const #2 = String   #44;    //  abc
    const #3 = String   #45;    //  ?abc
    const #44 = Asciz   abc;
    const #45 = Asciz   ?abc;
    

    字节码
    ldc #2; //String abc
    astore_1    //one
    
    ldc #3; //String ?abc
    iconst_1
    invokevirtual   #4; //Method String.substring:(I)Ljava/lang/String;
    astore_2    //two
    

    拳头字符串像往常一样构造。第二个是通过首先加载常量 "?abc" 创建的。字符串,然后调用 substring(1)在上面。

    输出

    毫不奇怪 - 我们有两个不同的字符串,指向两个不同的 char[]内存中的文字:
    27379847
    7615385
    8388097
    16585653
    

    嗯,文本并没有什么不同,equals()方法仍然会产生 true .我们有两个不必要的相同文本的副本。

    现在我们应该进行两个练习。首先,尝试运行:
    two = two.intern();
    

    在打印哈希码之前。不仅两者onetwo指向相同的文本,但它们是相同的引用!
    11108810
    11108810
    15184449
    15184449
    

    这意味着 one.equals(two)one == two测试将通过。我们也节省了一些内存,因为 "abc" text 在内存中只出现一次(第二个副本将被垃圾收集)。

    第二个练习略有不同,看看这个:
    String one = "abc";
    String two = "abc".substring(1);
    

    显然onetwo是两个不同的对象,指向两个不同的文本。但是为什么输出表明它们都指向相同的 char[]大批?!?
    23583040
    23583040
    11108810
    8918249
    

    我会把答案留给你。它会教你怎么做 substring()有效,这种方法的优点是什么以及何时可以使用 lead to big troubles .

    关于java - 字符串文字池是对字符串对象的引用的集合,还是对象的集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11700320/

    相关文章:

    java - EclipseLink MOXy 存储用 @XmlElement 注释的对象类型字段的 DOM 元素实例

    java - Hibernate:在我的类中添加一个未映射到数据库表的属性

    java - 在类本身中创建一个实例

    java - 从java中的另一个字符串中删除字符串

    string - Boyer More exact 子串是否匹配动态规划的范例?

    dynamic - 静态分配与动态分配与自动分配

    java - 发送嵌入 SOAP 消息中的 XML 数据的最佳实践是什么?

    javascript - JS 条件正则表达式,删除两个分隔符之间字符串的不同部分

    swift - swift 中带有选项的结构的大小

    c++ - 使用 C++ 从文件读取到单个内存块