是string文字池汇集对string对象或对象集合的引用

SCJP Tip Line的作者Corey McGlone写了一篇关于javaranch网站的文章,我感到很困惑。 命名为Strings, Kathy Sierra (co-founder of javaranch) and Bert BatesKathy Sierra (co-founder of javaranch) and Bert Bates编写的SCJP Java 6程序员指南。

我会试着引用科里先生和凯西·谢拉女士所引用的关于弦乐文字池的内容。

1.按照科里·麦克隆先生的说法:

  • string文字池是指向string对象的引用的集合。

  • String s = "Hello"; (假设堆上没有名为“Hello”的对象),将在堆上创build一个String对象"Hello" ,并将该对象的引用放置在String Literal Pool(常量表)

  • String a = new String("Bye"); (假设堆上没有名为“Bye”的对象, new运算符将使JVM在堆上创build一个对象。

现在对于创build一个String的"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." 在这种情况下,对这两个string的引用仍然被放到常量表(string文字池)中,但是当您使用关键字"new," ,JVM必须在运行时创build一个新的String对象,时间,而不是使用常数表中的一个。

这是解释它的图。

在这里输入图像描述

那么这是否意味着,string文字池也有这个对象的引用?

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

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

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

  • 为了使Java更高效地存储内存,JVM在编译器遇到string文字时,留出一个称为“string常量池”的特殊区域,检查该池是否已经存在。 如果不是那么它会创build一个新的string文字对象。

  • String s = "abc"; //创build一个String对象和一个引用variables….

    这很好,但现在下面的声明让我感到困惑。

  • String s = new String("abc") //创build两个对象和一个引用variables。

    它在书中说,….在正常(非池)内存中的一个新的String对象,“s”将引用它…而额外的文字“abc”将被放置在池中。

    本书中的上述内容与Corey McGlone的文章中的内容相冲突。

    • 如果String Literal Pool是Corey McGlone提到的String对象的引用的集合,那么如何将字面值对象“abc”放置在池中,如本书所述。

    • 而这个string文字池驻留在哪里。

请明白这个疑问,虽然编写代码并不重要,但是从内存pipe理的angular度来看非常重要,这就是我想要澄清这个问题的原因。

我认为这里要理解的重点是String Java对象和它的内容之间的区别 – char[]在私有value字段下 。 String基本上是char[]数组的封装,封装它,使其不可能修改,所以String可以保持不变。 而且String类还记得这个数组的哪个部分被实际使用(见下文)。 这一切都意味着你可以有两个不同的String对象(相当轻量)指向同一个char[]

我将向您展示几个示例,以及每个String hashCode()和内部char[] value字段(我将其称为text来区分string)的hashCode() )。 最后,我将显示javap -c -verbose输出,以及用于testing类的常量池。 请不要将类常量池与string文字池混淆。 他们不完全一样。 另请参见了解常量池的javap输出 。

先决条件

为了testing的目的,我创build了一个打破String封装的实用方法:

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

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

两个string文字在一个类中

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

Java代码

 String one = "abc"; String two = "abc"; 

顺便说一句,如果你只是写"ab" + "c" ,Java编译器将在编译时执行连接,生成的代码将完全相同。 这只适用于所有string在编译时已知的情况。

类常量池

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

这里是我们上面例子中常量池的内容。

 const #2 = String #38; // abc //... const #38 = Asciz abc; 

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

字节码

这里是生成的字节码。 请注意, onetwo引用都分配了相同的#2常量,指向"abc"string:

 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()testing将会通过。 但更多的是, onetwo是完全相同的参考! 所以one == two也是如此。 显然,如果onetwo指向同一个对象,那么one.valuetwo.value必须相等。

文字和new String()

Java代码

现在我们都等待的例子 – 一个string文字和一个新的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 

仔细地看! 第一个对象的创build方式与上面相同,不足为奇。 它只是对常量池中的已经创build的String#2 )进行常量引用。 然而,第二个对象是通过正常的构造函数调用创build的。 但! 第一个String作为parameter passing。 这可以反编译为:

 String two = new String(one); 

产量

输出有点令人惊讶。 表示对String对象的引用的第二对是可以理解的 – 我们创build了两个String对象 – 一个是在常量池中为我们创build的,第二对是为two手动创build的。 但为什么在地球上第一对表明这两个String对象指向相同的char[] value数组?

 41771 41771 8388097 16585653 

当你看看String(String)构造函数是如何工作的 (这里大大简化了)

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

看到? 当你创build一个新的String对象的时候,它会重用 char[] valueString是不可变的,不需要复制已知从未被修改的数据结构。

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

运行时修改和intern()

Java代码

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

 String one = "abc"; String two = "?abc".substring(1); //also two = "abc" 

Java编译器(至less是我的)在编译时并不够聪明,无法执行这样的操作,看看:

类常量池

突然之间,我们结束了两个常量string指向两个不同的常量文本:

 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 

第一个string像往常一样构build。 第二个是通过首先加载常量"?abc"string,然后调用substring(1)

产量

这里没有什么惊喜 – 我们有两个不同的string,指向内存中的两个不同的char[]文本:

 27379847 7615385 8388097 16585653 

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

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

 two = two.intern(); 

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

 11108810 11108810 15184449 15184449 

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

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

 String one = "abc"; String two = "abc".substring(1); 

显而易见, onetwo是两个不同的对象,指向两个不同的文本。 但是输出如何表明它们都指向相同的char[]数组?

 23583040 23583040 11108810 8918249 

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

我回头阅读CodeRanch文章,我不认为你所说的内容是精确的。 例如,你已经错过了在类加载时分配"Hello"的String对象的关键点,而不是在执行语句的时候。 我怀疑你对SCJP文本的总结同样是不准确的……而且由于你的阅读/总结不准确,你的“疑惑”(很大一部分)。

不幸的是,由于我们只能依靠你在SCJP案中的总结,所以很难回答你的问题是哪个文本是正确的。

我期望答案是两个都是对的,而且他们用不同的方式大致说了同样的话。

 In Java, there is no operator overloading whatsoever, and that's why the comparison operators are only overloaded for the primitive types. The 'String' class is not a primitive, thus it does not have an overloading for '==' and uses the default of comparing the address of the object in the computer's memory. I'm not sure, but I think that in Java 7 or 8 oracle made an exception in the compiler to recognize str1 == str2 as str1.equals(str2)