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

时间:2014-11-25 09:36:42

标签: java string performance jvm pool

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

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

This post澄清了==运算符的使用情况以及new的缺席情况,并说这是由于String文字被实习或取自<{1}}的文字池,因此JVM是不可变的。

看到诸如

之类的陈述
Strings

第一次真正发生了什么?

  1. String s="hi" 是否会像JVM一样替换它, 其中创建了一个Object,并将String s=new String("hi")添加到 String中 文字池以及后续调用,例如"hi" 从泳池中取出?

  2. 这是底层机制的运作方式吗?如果是这样,那么

    String s1="hi"

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

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

  3. 此外,我们可以通过哪种方式访问​​字符串池 检查其中有多少String s="Test"; String s1="Test"; 个文字,占用了多少空间等?

7 个答案:

答案 0 :(得分:14)

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");
    

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

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

答案 1 :(得分:9)

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

    JVM是否像String s=new String("hi")一样替换它,   其中创建了一个对象并且&#34; hi&#34;被添加到String文字中   池等后续调用,如String s1 =&#34; hi&#34;取自   游泳池?。

没有。真正发生的是 - String Literals 在编译期间已解析并且 interned (添加到String常量池),只要该类是加载/初始化懒洋洋地。因此,它们可供JVM中的类使用。 请注意,即使在字符串常量池中有一个值为"hi"的String,new String("hi")也将创建堆上的另一个String并返回其引用。

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

相同
 String s="Test"; 
 String s1="Test"; 
  

在内存利用率方面   效率η

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

  
      
  1. 此外,如果我们有任何方式可以访问字符串池   检查它中存在多少个字符串文字,占用空间等   来自该计划或任何监测工具?
  2.   

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

答案 2 :(得分:6)

这与主题没有密切关系,但每当你怀疑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

答案 3 :(得分:6)

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

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

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

答案 4 :(得分:5)

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

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

  3. 是的,使用HotSpot Serviceability Agent。这是example

答案 5 :(得分:0)

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

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

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

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

答案 6 :(得分:-2)

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

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

两者都存储在堆中并引用单个&#34;测试&#34;因此s1 = s, 而

String s=new String("Test");

是一个也存储在堆中但不同形式的对象s1 = s 参考here