Java中字符串池的基础机制?

我很好奇为什么可以在不调用new String()情况下创建new String() ,因为API提到它是class java.lang.String

那么我们如何才能使用String s="hi"而不是String s=new String("hi")

这篇文章阐明了使用==运算符和缺少new并说这是由于String文字被JVM 中断或从文字池中取出,因此Strings是不可变的。

看到诸如此类的陈述

 String s="hi" 

第一次真正发生了什么?

  1. JVM是否将其替换为String s=new String("hi") ,其中创建了一个Object并且将"hi"添加到String文本池中 ,因此后续调用(如String s1="hi"取自池?

  2. 这是基础机制的运作方式吗? 如果是的话,那就是

     String s=new String("Test"); String s1="Test"; 

    同样的

     String s="Test"; String s1="Test"; 

    在内存利用率和效率方面

  3. 另外,有什么办法可以访问字符串池来检查它中存在多少String文字,占用了多少空间等等?

  1. String s="hi"第一次真正发生了什么?

JVM是否将其替换为String s=new String("hi") ,其中创建了一个Object并且将“hi”添加到String文本池中,因此后续调用(如String s1 =“hi”)取自游泳池?

不会。真正发生的事情是 – 字符串文字在编译期间被解析并加载/初始化懒惰加密 (添加到字符串常量池)。 因此,它们可供JVM中的类使用。 请注意,即使在字符串常量池中有一个值为"hi"的String, new String("hi")也会在堆上创建另一个String并返回其引用。

  String s=new String("Test"); String s1="Test"; 

同样的

  String s="Test"; String s1="Test"; 

在内存利用率和效率方面?

不,在第一种情况下,创建2个“测试”字符串。 一个将被添加到String常量池(假设它不存在)和另一个在堆上。 第二种情况可以是GCed。在第二种情况下,String常量池中只有一个字符串文字 ,并且有2个引用( ss1 )。

  1. 另外,如果有任何方法我们可以访问字符串池,检查它中存在多少字符串文字,空间占用等来自程序或任何监视工具?

我认为我们不能看到String常量池的内容。 我们只能基于我们的假设来假设确认行为。

Java编译器对字符串文字有特殊支持。 假设它没有,那么在源代码中创建字符串真的很麻烦,你必须写下这样的东西:

 // Suppose that we would not have string literals like "hi" String s = new String(new char[]{ 'h', 'i' }); 

回答你的问题:

  1. 或多或少,如果你真的想知道细节,你必须研究JVM的源代码,你可以在OpenJDK找到它,但要注意它是巨大而复杂的。

  2. 不,这两个不相同。 在第一种情况下,您将显式创建一个新的String对象:

     String s=new String("Test"); 

    它将包含由文字"Test"表示的String对象的副本。 请注意,在Java中编写new String("some literal") 永远不是一个好主意 – 字符串是不可变的,并且永远不需要复制字符串文字。

  3. 我不知道要检查字符串池中的内容。

这与主题没有密切关系,但每当你怀疑java编译器会做什么时,你可以使用

 javap -c CompiledClassName 

打印实际发生的事情。 (来自dir的CompiledClassName,其中CompiledClassName.class是)

要添加到Jesper的答案,有更多机制在起作用,例如当您从文字或最终变量连接String时,它仍将使用实习池:

 String s0 = "te" + "st"; String s1 = "test"; final String s2 = "te"; String s3 = s2 + "st"; System.out.println(s0==s1); //true System.out.println(s3==s1); //true 

但是当你使用非final变量连接时,它将不使用池:

 String s0 = "te"; String s1 = s0 + "st"; String s2 = "test"; System.out.println(s1 == s2); //false 

以下是略微简化,因此不要试图引用它的确切细节,但一般原则适用。

每个编译的Java类都包含一个数据blob,它指示在该类文件中声明了多少个字符串,每个字符串的长度以及属于所有字符串的字符。 加载类时,类加载器将创建一个合适大小的String[]来保存该类中定义的所有字符串; 对于每个字符串,它将生成一个合适大小的char[] ,从类文件中读取适当数量的字符到char[] ,创建一个封装这些字符的String ,并将引用存储到类的String[]

在编译某个类(例如Foo )时,编译器知道它遇到的第一,第二,第三,第五等字符串文字。如果代码说myString = "George"; 而George是第六个字符串文字,它将作为“加载字符串文字#6”指令出现在代码中; 正常时编译器在为该指令生成代码时,将生成一条指令以获取与该类关联的第六个字符串引用。

  1. 一种,但不完全是。
    在常量池解析期间创建和实现字符串常量。 这在第一次执行加载字符串文字的LDC字节码时发生。 第一次执行后,JVM将JVM_CONSTANT_UnresolvedString常量池标记替换为JVM_CONSTANT_String标记,以便下次LDC将获取现有字符串而不是创建新字符串。

  2. 不。第一次使用"Test"将创建一个新的字符串对象。 然后new String("Test")将创建第二个对象。

  3. 是的,使用HotSpot Serviceability Agent 。 这是一个例子 。

我相信创建String的基础机制是StringBuilder,它最后组装String对象。 至少我知道如果你有一个你想要改变的字符串,例如:

 String str = "my String"; // and then do System.out.println(str + "new content"); 

所以它的作用是从旧对象创建一个StrigBuilder,并将其替换为从构建器构造的新对象。 这就是为什么使用StringBuilder而不是常规字符串来增加内存效率的原因。

有一种方法可以使用String.intern()方法访问已创建的String池。 它告诉java为相同的字符串使用相同的内存空间,并在内存中为您提供对该位置的引用。 这也允许您使用==运算符来比较字符串,并且内存效率更高。

字符串池,因为它是存储在堆中的字符串池,用于exp:

 String s="Test"; String s1="Test"; 

两者都存储在堆中并引用单个“测试”,因此s1 = s,而

 String s=new String("Test"); 

是一个也存储在堆中的对象,但不同的formss1 = s 在这里引用