String Literal Pool是String对象或对象集合的引用的集合

Kum*_*tra 69 java string memory-management

看完SCJP Tip Line的作者Corey McGlone撰写关于javaranch网站的文章后,我感到很困惑.命名为Strings,Literally和SCJP Java 6程序员指南,由Kathy Sierra(javaranch的联合创始人)和Bert Bates撰写.

我将尝试引用Corey先生和Kathy Sierra女士引用的关于String Literal Pool的内容.

1.据Corey McGlone先生说:

  • String Literal Pool是指向String对象的引用的集合.

  • String s = "Hello"; (假设Heap上没有名为"Hello"的对象),将"Hello"在堆上创建一个String对象,并在String Literal Pool(Constant Table)中放置对该对象的引用

  • String a = new String("Bye");(假设Heap上没有名为"Bye" new的对象,操作符将强制JVM在堆上创建一个对象.

现在,"new"对于创建String及其引用的运算符的解释 在本文中有点令人困惑,所以我将文章本身的代码和解释放在下面.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,由于关键字,我们实际上最终会遇到略微不同的行为."new." 在这种情况下,对两个String文字的引用仍然放在常量表(String Literal Pool)中,但是当你来到关键字时"new,"JVM必须在运行时创建一个新的String对象,而不是使用常量表中的那个.

这是解释它的图表..

在此输入图像描述

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

以下是Corey McGlone撰写文章的链接

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

2.据SCJP书中的Kathy Sierra和Bert Bates所说:

  • 为了使Java更具内存效率,JVM预留了一个称为"字符串常量池"的特殊内存区域,当编译器遇到字符串文字时,它会检查池以查看是否已存在相同的字符串.如果没有,则创建一个新的String Literal Object.

  • String s = "abc"; //创建一个String对象和一个引用变量....

    那很好,但后来我对此声明感到困惑:

  • String s = new String("abc") //创建两个对象和一个引用变量.

    它在书中说....正常(非池)内存中的一个新String对象,而"s"将引用它......而另一个文字"abc"将放在池中.

    书中的上述内容与Corey McGlone撰写的文章中的内容相冲突.

    • 如果String Literal Pool是Corey McGlone所提到的String对象的引用的集合,那么为什么将文字对象"abc"放在池中(如书中所提到的)?

    • 这个String Literal Pool在哪里?

请清楚这个疑问,虽然在编写代码时无关紧要,但从内存管理方面来说非常重要,这就是我想清除这个基础的原因.

Tom*_*icz 108

我认为这里要理解的主要问题是StringJava对象及其内容之间的区别- char[]私有value字段下.String基本上是char[]数组的包装器,封装它并使其无法修改,因此String可以保持不可变.另外,String类记住此阵列的部分被实际使用(见下文).这意味着你可以有两个不同的String对象(非常轻量级)指向相同的对象char[].

我会告诉你一些例子,连同hashCode()每一个StringhashCode()内部的char[] value领域(我将其称之为文本从字符串相区别).最后,我将为javap -c -verbose我的测试类显示输出和常量池.请不要将类常量池与字符串文字池混淆.它们并不完全相同.另请参阅了解常量池的javap输出.

先决条件

为了测试的目的,我创建了一个打破String封装的实用方法:

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

这将打印hashCode()char[] value,有效地帮助我们理解这是否特定String指向相同的char[]文字或没有.

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

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

Java代码

String one = "abc";
String two = "abc";
Run Code Online (Sandbox Code Playgroud)

顺便说一句,如果你只是写"ab" + "c",Java编译器将在编译时执行连接,生成的代码将完全相同.这仅在编译时知道所有字符串时才有效.

类常量池

每个类都有自己的常量池 - 一个常量值列表,如果它们在源代码中出现多次,可以重复使用.它包括常见的字符串,数字,方法名称等.

以下是上面示例中常量池的内容.

const #2 = String   #38;    //  abc
//...
const #38 = Asciz   abc;
Run Code Online (Sandbox Code Playgroud)

需要注意的重要事项是字符串指向的String常量object(#2)和Unicode编码的text "abc"(#38)之间的区别.

字节码

这是生成的字节码.请注意,两个onetwo引用都分配有#2指向"abc"字符串的相同常量:

ldc #2; //String abc
astore_1    //one
ldc #2; //String abc
astore_2    //two
Run Code Online (Sandbox Code Playgroud)

产量

对于每个示例,我打印以下值:

System.out.println(showInternalCharArrayHashCode(one));
System.out.println(showInternalCharArrayHashCode(two));
System.out.println(System.identityHashCode(one));
System.out.println(System.identityHashCode(two));
Run Code Online (Sandbox Code Playgroud)

毫无疑问,这两对是平等的:

23583040
23583040
8918249
8918249
Run Code Online (Sandbox Code Playgroud)

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

文字和 new String()

Java代码

现在我们都在等待的例子 - 一个字符串文字和一个String使用相同文字的新文字.这将如何运作?

String one = "abc";
String two = new String("abc");
Run Code Online (Sandbox Code Playgroud)

"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
Run Code Online (Sandbox Code Playgroud)

仔细看!第一个对象以与上面相同的方式创建,毫不奇怪.它只需要从常量池中继续引用已创建的String(#2).但是,第二个对象是通过普通的构造函数调用创建的.但!第一个String作为参数传递.这可以反编译为:

String two = new String(one);
Run Code Online (Sandbox Code Playgroud)

产量

输出有点令人惊讶.表示String对象引用的第二对是可以理解的 - 我们创建了两个String对象 - 一个是在常量池中为我们创建的,另一个是为手动创建的two.但是,为什么在地球上第一对表明两个String物体都指向同一个char[] value阵列?!

41771
41771
8388097
16585653
Run Code Online (Sandbox Code Playgroud)

当你看看String(String)构造函数是如何工作的时候会变得很清楚(这里大大简化了):

public String(String original) {
    this.offset = original.offset;
    this.count = original.count;
    this.value = original.value;
}
Run Code Online (Sandbox Code Playgroud)

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

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

运行时修改和 intern()

Java代码

假设您最初使用了两个不同的字符串,但经过一些修改后它们都是相同的:

String one = "abc";
String two = "?abc".substring(1);  //also two = "abc"
Run Code Online (Sandbox Code Playgroud)

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

类常量池

突然间,我们最终得到了两个指向两个不同常量文本的常量字符串:

const #2 = String   #44;    //  abc
const #3 = String   #45;    //  ?abc
const #44 = Asciz   abc;
const #45 = Asciz   ?abc;
Run Code Online (Sandbox Code Playgroud)

字节码

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
Run Code Online (Sandbox Code Playgroud)

第一根弦就像往常一样构造.第二个是通过首先加载常量"?abc"字符串然后调用substring(1)它来创建的.

产量

这里不足为奇 - 我们有两个不同的字符串,指向char[]内存中的两个不同的文本:

27379847
7615385
8388097
16585653
Run Code Online (Sandbox Code Playgroud)

那么,文本并没有真正不同,equals()方法仍然会产生true.我们有两个不必要的同一文本副本.

现在我们应该进行两次练习.首先,尝试运行:

two = two.intern();
Run Code Online (Sandbox Code Playgroud)

在打印哈希码之前.不仅双方onetwo指向相同的文字,但它们是相同的参考!

11108810
11108810
15184449
15184449
Run Code Online (Sandbox Code Playgroud)

这意味着两者one.equals(two)one == two测试都将通过.我们还保存了一些内存,因为"abc"文本只在内存中出现一次(第二个副本将被垃圾回收).

第二个练习略有不同,请查看:

String one = "abc";
String two = "abc".substring(1);
Run Code Online (Sandbox Code Playgroud)

显然,onetwo是两个不同的对象,指向两个不同的文本.但是为什么输出表明他们都指向同一个char[]阵列?!?

23583040
23583040
11108810
8918249
Run Code Online (Sandbox Code Playgroud)

我会把答案留给你.它将教你如何substring()工作,这种方法的优点是什么,何时会导致大麻烦.

  • **String Literal Pool是String对象的引用的集合,还是对象的集合**,但究竟答案是什么? (6认同)
  • 从Java 7开始,substring会创建所需数组部分的新副本,而不是指向同一个数组. (3认同)
  • 该答案未提供初始问题的答案。字符串池在内部是什么样的?它是引用的集合还是字符串的集合? (3认同)
  • +1,感谢您提供有关String的深入工作知识.但上面提到的信息是我已经有了一个想法...我的问题仍然存在...字符串文字池是一个对象或参考的集合...如果字符串文字池持有对象,所以将使用新的运算符将创建两个String对象,一个在外部,一个在内存池中,引用指向内存池外的一个. (2认同)